KibbleProvider
Provide the client and display options once so every Form can omit them.
Wrap your app (or a layout) once with KibbleProvider and every Form below it finds the client through context. It also carries site-wide display options: theme, size, classNames, requiredIndicator and optionalIndicator.
Preview
KibbleProvider renders nothing visible. See the required indicator preview for its effect.
Usage
// app/providers.tsx (Next.js app router: a client component, because the client holds functions)
'use client';
import { KibbleProvider } from 'kibbleforms';
import { kibble } from '@/kibble/client';
export function Providers({ children }: { children: React.ReactNode }) {
return <KibbleProvider client={kibble}>{children}</KibbleProvider>;
}
// any page, even a server component: the schema is plain data
import { Form } from 'kibbleforms';
import { waitlist } from '@/kibble/forms/waitlist';
export default function Page() {
return <Form schema={waitlist} />;
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
client |
KibbleClient |
inherited from a parent provider | From createClient. Optional when nesting: a provider without client keeps the parent's. |
requiredIndicator |
ReactNode | null |
inherited, else nothing | What Label appends to required fields. |
optionalIndicator |
ReactNode | null |
inherited, else muted "Optional" | What Label appends to optional fields. null for nothing. |
theme |
KibbleTheme |
inherited | Colors, radius, font, colorScheme for every form below; merged per key with a parent provider's and with each form's (the form wins). See Theming. |
size |
'sm' | 'md' | 'lg' |
inherited, else 'md' |
Default size for every form below; a form's size wins. |
classNames |
Partial<Record<Slot, string>> |
inherited | Classes per part for every form below. Per slot, the parent's, this provider's and the form's classes are all kept, in that order, then the component's own className. |
children |
ReactNode |
— |
Renders no DOM element and has no className or data-kb.
Hooks: useKibbleClient() returns the nearest client or null; useFormOptions() returns the merged display options; useSlot(slot) returns the merged classes for one slot (for your own components). layout is a Form prop only. Form scopes its own options (merged over the provider's) to its subtree.
Variants and states
Nesting merges: an inner provider can change one option and keep the rest.
<KibbleProvider client={kibble} theme={{ primary: 'var(--brand)', radius: 'lg' }} size="sm" classNames={{ label: 'uppercase tracking-wide' }}>
<Form schema={waitlist} /> {/* brand primary, lg radius, sm, uppercase labels */}
<Form schema={demo} size="md" theme={{ colorScheme: 'dark' }} /> {/* same, but md and dark */}
</KibbleProvider>
<KibbleProvider client={kibble} optionalIndicator={<span> (optional)</span>}>
…most forms…
<KibbleProvider requiredIndicator={<span> (required)</span>} optionalIndicator={null}>
<Form schema={application} /> {/* same client, different marks */}
</KibbleProvider>
</KibbleProvider>
A Form with its own client prop ignores the context client; requiredIndicator on Form overrides the provider's for that form only.
Customization
Renders no DOM. theme becomes inline variables on each Form root; you can also set --kb-* variables on :root or a wrapper (see Styling).
Accessibility
No effect on its own; see Label for how the required indicator is exposed (aria-hidden mark, aria-required on the control).