Styling
The styled components are authored in Tailwind v4 and read --kb-* CSS variables and two data-kb-* attributes, so the Tailwind source and the compiled CSS look and behave the same. There are five layers, from "do nothing" to "own every class". Most sites stop at the first or second. The interactive Theming page shows each knob live.
Import the CSS (once)
kibble init does this for you. By hand:
Tailwind v4. Your Tailwind build generates the utilities the components use, so your own classes override theirs normally:
/* app/globals.css */
@import "tailwindcss";
@import "kibbleforms/theme.css";
@import "kibbleforms/themes/shadcn.css"; /* only on shadcn/ui sites */
@source "../node_modules/kibbleforms/dist";
Anything else (Tailwind v3, plain CSS, CSS modules). Import the compiled stylesheet. It is scoped under [data-kibble] (the Form root), so it does not touch the rest of the page and has no global reset. On a shadcn/ui site with Tailwind v3, add kibbleforms/themes/shadcn-v3.css after it.
import 'kibbleforms/styles.css';
1. Inherit the site
The form uses your page's font (--kb-font: inherit). On shadcn/ui sites, themes/shadcn.css maps every --kb-* token to shadcn's variables (--primary, --primary-foreground, --background, --foreground, --muted, --muted-foreground, --input, --ring, --destructive, --radius), so the form matches your buttons and inputs and changes with your theme. themes/shadcn-v3.css does the same for the HSL-triplet style (hsl(var(--primary))).
2. Dark mode follows the site
The built-in dark values apply when the form sits under a .dark or [data-theme="dark"] ancestor, which is what next-themes and most Tailwind setups toggle. To force it on one form, pass theme={{ colorScheme: 'dark' }} ('light' forces light, 'auto' follows the visitor's OS). A form is never dark because of the OS alone unless you ask for 'auto'. Override the dark values with your own rule:
.dark { --kb-primary: #a5b4fc; --kb-primary-fg: #1e1b4b; }
3. Tokens and the theme prop
Every color, the radius, font, sizes and spacing are CSS variables. Set them on :root, on .dark, or on any wrapper:
:root {
--kb-primary: #111827; --kb-primary-fg: #ffffff;
--kb-bg: #ffffff; --kb-fg: #111827;
--kb-muted: #f3f4f6; --kb-muted-fg: #6b7280;
--kb-border: #e5e7eb; --kb-ring: #2563eb;
--kb-error: #dc2626; --kb-input-bg: #ffffff;
--kb-radius: 0.5rem; --kb-font: inherit;
--kb-control-h: 2.5rem; --kb-text: 15px;
--kb-border-width: 1px; --kb-label-weight: 500;
--kb-space: 1.25rem; /* between fields */
--kb-gap: 0.5rem; /* inside a field */
}
Or pass theme to one form (or to KibbleProvider for all of them; the form's keys win). It becomes inline variables on the form root:
<Form schema={waitlist} theme={{ primary: '#2563eb', radius: 'full', font: 'var(--font-sans)' }} />
4. size and layout
size="sm" | "md" | "lg" swaps one bundle of tokens (control height, text size, spacing) through data-kb-size. layout={{ columns: 2 }} lays the auto-rendered fields (and Page) out in a grid; long text, choices, consent and keys listed in layout.full span every column, and everything is one column below 640px.
<Form schema={demo} size="lg" layout={{ columns: 2, full: ['company_url'] }} />
5. classNames slots, className, and owning the source
classNames adds classes to every instance of a part: root, form, page, pageHeader, field, label, help, input (Input, Textarea, Select, DatePicker), option (choice rows), segment (YesNo), rating, consent, error, submit, button (Back, Next), progress, success. On KibbleProvider they apply to every form (both are kept, the form's after the provider's). They are merged with tailwind-merge before each component's own className, so a conflicting utility on the component wins:
<Form schema={waitlist} classNames={{ label: 'uppercase tracking-wide', input: 'bg-zinc-50', submit: 'rounded-full' }} />
<Submit className="w-auto" /> {/* composed: beats classNames.submit and the default */}
Plain CSS can target the data-kb hooks ([data-kb="input"], [data-kb="submit"], …) and [data-kb-size], [data-kb-color-scheme].
To change the markup itself, start from kibbleforms/headless with your own elements (Headless); the styled components are built on the same hooks.