Get started
shadcn/ui
One CSS import points every shadcn/ui variable at Flint. Values and dark mode stay Flint's.
Import the bridge
In your Tailwind v4 entry CSS:
@import "tailwindcss";
@import "@innerspark/flint/tokens.css";
@import "@innerspark/flint/shadcn.css"; Dark mode follows data-theme="dark", the same attribute as everywhere else in Flint. The bridge also points Tailwind's dark: variant at it, so you need no .dark class.
How the variables map
Each shadcn variable becomes a reference to a Flint variable. The bridge picks no values of its own.
| shadcn variable | Flint variable | Flint token |
|---|---|---|
--background | --color-surface-page | semantic.surface.page |
--foreground | --color-text-default | semantic.text.default |
--card | --color-surface-default | semantic.surface.default |
--card-foreground | --color-text-default | semantic.text.default |
--popover | --color-surface-default | semantic.surface.default |
--popover-foreground | --color-text-default | semantic.text.default |
--primary | --color-action-primary | semantic.action.primary |
--primary-foreground | --color-action-on-primary | semantic.action.onPrimary |
--secondary | --color-surface-subtle | semantic.surface.subtle |
--secondary-foreground | --color-text-heading | semantic.text.heading |
--muted | --color-surface-subtle | semantic.surface.subtle |
--muted-foreground | --color-text-muted | semantic.text.muted |
--accent | --color-selected-surface | semantic.selected.surface |
--accent-foreground | --color-selected-text | semantic.selected.text |
--destructive | --color-action-danger | semantic.action.danger |
--destructive-foreground | --color-action-on-danger | semantic.action.onDanger |
--border | --color-border-default | semantic.border.default |
--input | --color-border-control | semantic.border.control |
--ring | --color-action-focus-ring | semantic.action.focusRing |
--radius | --radius-control | radius.control |
--chart-1 | --color-chart-1 | semantic.chart.1 |
--chart-2 | --color-chart-2 | semantic.chart.2 |
--chart-3 | --color-chart-3 | semantic.chart.3 |
--chart-4 | --color-chart-4 | semantic.chart.4 |
--chart-5 | --color-chart-5 | semantic.chart.5 |
--sidebar | --color-surface-default | semantic.surface.default |
--sidebar-foreground | --color-text-default | semantic.text.default |
--sidebar-primary | --color-action-primary | semantic.action.primary |
--sidebar-primary-foreground | --color-action-on-primary | semantic.action.onPrimary |
--sidebar-accent | --color-selected-surface | semantic.selected.surface |
--sidebar-accent-foreground | --color-selected-text | semantic.selected.text |
--sidebar-border | --color-border-default | semantic.border.default |
--sidebar-ring | --color-action-focus-ring | semantic.action.focusRing |
Why secondary is a quiet surface
Flint's action.secondary is a bright sky blue. White text on it fails contrast, so shadcn's secondary maps to surface.subtle with heading text instead.
Restyle the components
The bridge fixes colors. shadcn's components still ship with their own sizes, a 50% transparent focus ring and a 16px checkbox. Restyle each one onto Flint's component tokens before you call it Flint. The button, for example:
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-[var(--button-radius)] min-h-[var(--button-height)] px-4 text-sm font-bold focus-visible:[outline:var(--focus-ring-width)_solid_var(--focus-ring-color)] focus-visible:[outline-offset:var(--focus-ring-offset)]',
{
variants: {
variant: {
default: 'bg-[var(--button-primary-bg)] text-[var(--button-primary-fg)] hover:bg-[var(--button-primary-bg-hover)]',
secondary: 'border border-[var(--button-secondary-border)] bg-[var(--button-secondary-bg)] text-[var(--button-secondary-fg)]',
destructive: 'bg-[var(--button-danger-bg)] text-[var(--button-danger-fg)] hover:bg-[var(--button-danger-bg-hover)]',
},
},
},
); Flint versions of Button, Input and Checkbox live in the workbench, with their source in docs/src/workbench/ui.