Skip to main content
Introducing packages.sweber.dev
Documentation menuReact components

React components

InverseLink, WithdrawalForm, CancellationForm and the useDeclarationFlow hook.

pnpm add @sweberdev/inverse-react

The components work with React 18 and 19 and are client components ("use client" is set).

<InverseLink kind="withdrawal" href="/widerruf" />        // Vertrag widerrufen
<InverseLink kind="cancellation" href="/kuendigen" />     // Verträge hier kündigen
<InverseLink kind="cancellation" href="/en/cancel" locale="en" /> // Cancel contracts here

Pass children only if you are sure the wording is equivalent. The statutory wording is the safe choice.

<WithdrawalForm> and <CancellationForm>

Prop
endpointURL of your createInverseHandler route
localede (default), en, fr, it
defaultValuesprefill name, email, contractRef and others
showItemswithdrawal: field for a partial withdrawal
showMessageoptional free-text message
introreplaces the intro text
messagesoverride single texts, e.g. { withdrawal: { intro: "…" } }
onSuccesscalled with { id, receivedAt, copy, endsAt? }
submitreplace the fetch call, e.g. for tests

The flow has three steps: details, review with the statutory confirm button, and the receipt with a download. Focus moves to the heading on every step and errors are linked to their fields with aria-describedby.

Styling

The components are unstyled. Every element has a class:

.inverse-flow { display: grid; gap: 1rem; max-width: 32rem; }
.inverse-field { display: grid; gap: 0.25rem; }
.inverse-field[data-invalid] input { border-color: #b91c1c; }
.inverse-error { color: #b91c1c; }
.inverse-summary { display: grid; grid-template-columns: max-content 1fr; gap: 0.25rem 1rem; }
.inverse-summary div { display: contents; }
.inverse-actions { display: flex; gap: 0.5rem; }
.inverse-confirm { font-weight: 600; }
.inverse-copy { white-space: pre-wrap; }

The root has data-kind and data-step (form, review, sending, done).

Your own UI

import { useDeclarationFlow } from "@sweberdev/inverse-react";

const flow = useDeclarationFlow({ kind: "cancellation", endpoint: "/api/inverse" });
// flow.step, flow.values, flow.setValue, flow.errorFor(field),
// flow.review(), flow.back(), flow.confirm(), flow.summary, flow.result, flow.messages

Keep the two steps and the labels from flow.messages (withdrawal.confirm, cancellation.confirm).