Getting Started
Theming
baseui-cn uses CSS custom properties for theming. Tokens are defined as raw color values and mapped into Tailwind utilities through @theme, so utility classes like bg-primary/50.
Change the standard semantic variables in your stylesheet. For example, accent controls persistent selection, ring controls keyboard focus, and primary controls primary actions. Init and component installation add missing variables and preserve existing values. Class-based dark mode follows the application theme choice. Components include any required layout CSS when installed; you do not need a separate token vocabulary.
Choose your starting theme
Iris is the default color identity, with Rounded (0.75rem) geometry. Choose a preset when creating an app or explicitly update an existing theme. Color changes primary, accent and focus variables; Graphite surfaces and semantic success, warning, info and destructive colors remain independent.
iris — Iris
blue — Blue
violet — Violet
emerald — Emerald
orange — Orange
rose — Rose
neutral — Neutral
Radius presets: Sharp (sm: 0.25rem) · Medium (md: 0.5rem) · Rounded (lg: 0.75rem) · Extra (xl: 1rem).
npx baseui-cn@latest create my-app --framework next --color iris --radius lg
# Explicitly replace color and radius in an existing app
npx baseui-cn@latest init --color emerald --radius xl
# Preserve existing custom values
npx baseui-cn@latest init -yRepeated init without theme flags preserves existing values. Interactive reinitialization offers Keep existing values first. A color flag changes only color identity; a radius flag changes only radius. All values are ordinary CSS in your project, ready to edit.
CSS variables
Added to your configured stylesheet, such as app/globals.css or src/styles.css, by npx baseui-cn init.
@custom-variant dark (&:where(.dark, .dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
--color-info: var(--info);
--color-info-foreground: var(--info-foreground);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.75rem;
--background: #f8f9fb;
--foreground: #20242c;
--card: #ffffff;
--card-foreground: #20242c;
--popover: #ffffff;
--popover-foreground: #20242c;
--secondary: #e8ecf4;
--secondary-foreground: #20242c;
--muted: #f4f4f5;
--muted-foreground: #5e6674;
--border: #dedee3;
--input: #7d8797;
--destructive: #b02a3b;
--destructive-foreground: #ffffff;
--success: #22673f;
--success-foreground: #ffffff;
--warning: #875300;
--warning-foreground: #ffffff;
--info: #2457b2;
--info-foreground: #ffffff;
--primary: #5b55c9;
--primary-foreground: #ffffff;
--accent: #eff0ff;
--accent-foreground: #514bb5;
--ring: #6673e8;
}
.dark {
--background: #101011;
--foreground: #eeeeef;
--card: #171718;
--card-foreground: #eeeeef;
--popover: #1c1c1f;
--popover-foreground: #eeeeef;
--secondary: #303034;
--secondary-foreground: #eeeeef;
--muted: #29292d;
--muted-foreground: #aaaab3;
--border: #3d3d43;
--input: #6c6c75;
--destructive: #ff9da8;
--destructive-foreground: #271217;
--success: #92ddb0;
--success-foreground: #12251a;
--warning: #f4c876;
--warning-foreground: #281d0c;
--info: #a4c4ff;
--info-foreground: #13213b;
--primary: #a8aeff;
--primary-foreground: #18181a;
--accent: #25252e;
--accent-foreground: #b5b9ff;
--ring: #9699d6;
}
/* baseui-cn foundation */
@layer base {
:root { color-scheme: light; }
.dark { color-scheme: dark; }
}
@keyframes loading-spin { to { transform: rotate(360deg); } }
@utility loading-spinner { animation: loading-spin 1s linear infinite; }
@media (prefers-reduced-motion: reduce) {
.loading-spinner { animation: none; }
}
Tailwind v4 theme mapping
Add this @theme block so utilities like bg-primary and text-muted-foreground resolve to your variables.
@import "tailwindcss";
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
--color-info: var(--info);
--color-info-foreground: var(--info-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}Customizing your theme
Override any variable in your :root. Changes propagate directly to semantic utilities. Use accent and accent-foreground together for selected surfaces and markers, ring for keyboard focus, and primary with primary-foreground for primary actions. Supply complete CSS colors and verify the contrast of custom foreground/background pairs.
/* Example: start with Iris, then customize locally */
:root {
--background: #f8f9fb;
--foreground: #20242c;
--card: #ffffff;
--card-foreground: #20242c;
--popover: #ffffff;
--popover-foreground: #20242c;
--secondary: #e8ecf4;
--secondary-foreground: #20242c;
--muted: #f4f4f5;
--muted-foreground: #5e6674;
--border: #dedee3;
--input: #7d8797;
--destructive: #b02a3b;
--destructive-foreground: #ffffff;
--success: #22673f;
--success-foreground: #ffffff;
--warning: #875300;
--warning-foreground: #ffffff;
--info: #2457b2;
--info-foreground: #ffffff;
--primary: #5b55c9;
--primary-foreground: #ffffff;
--accent: #eff0ff;
--accent-foreground: #514bb5;
--ring: #6673e8;
--radius: 0.75rem;
}
.dark {
--background: #101011;
--foreground: #eeeeef;
--card: #171718;
--card-foreground: #eeeeef;
--popover: #1c1c1f;
--popover-foreground: #eeeeef;
--secondary: #303034;
--secondary-foreground: #eeeeef;
--muted: #29292d;
--muted-foreground: #aaaab3;
--border: #3d3d43;
--input: #6c6c75;
--destructive: #ff9da8;
--destructive-foreground: #271217;
--success: #92ddb0;
--success-foreground: #12251a;
--warning: #f4c876;
--warning-foreground: #281d0c;
--info: #a4c4ff;
--info-foreground: #13213b;
--primary: #a8aeff;
--primary-foreground: #18181a;
--accent: #25252e;
--accent-foreground: #b5b9ff;
--ring: #9699d6;
}
/* Custom status colors */
:root {
--success: oklch(0.7 0.16 150);
--success-foreground: oklch(0.985 0 0);
--warning: oklch(0.8 0.16 80);
--warning-foreground: oklch(0.21 0 0);
--info: oklch(0.68 0.14 250);
--info-foreground: oklch(0.985 0 0);
}Dark mode
Uses the .dark class strategy. Add the class to your <html> element. We recommend next-themes for automatic system preference detection.
import { ThemeProvider } from "next-themes"
export default function RootLayout({ children }) {
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
)
}pnpm add next-themesBase UI data attributes
Base UI applies these attributes to components. Use them in Tailwind for state-based styling.
data-[open]Overlay or disclosure is opendata-[closed]Overlay is closed — use for exit animationsdata-[checked]Checkbox, switch, or radio is checkeddata-[indeterminate]Checkbox is indeterminatedata-[disabled]Component is disableddata-[highlighted]List item is focused/highlighteddata-[selected]Select item is selecteddata-[starting-style]Entry animation start statedata-[side=top|bottom|left|right]Floating element placement/* Use data attributes for state-based Tailwind classes */
className="data-open:opacity-100 data-closed:opacity-0 transition-opacity"
className="data-checked:bg-primary data-checked:border-primary"
className="data-highlighted:bg-accent data-highlighted:text-accent-foreground"Accordion & Collapsible animations
Add these keyframes to your main stylesheet for smooth height transitions.
@layer utilities {
@keyframes accordion-down {
from { height: 0; opacity: 0; }
to { height: var(--accordion-content-height); opacity: 1; }
}
@keyframes accordion-up {
from { height: var(--accordion-content-height); opacity: 1; }
to { height: 0; opacity: 0; }
}
.animate-accordion-down {
animation: accordion-down 0.2s ease-out;
}
.animate-accordion-up {
animation: accordion-up 0.2s ease-out;
}
}