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).

bash
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 -y

Repeated 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.

app/globals.css
@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; }
}
`destructive`, `success`, `warning`, and `info` are all first-class semantic tokens. Use them for badges, toasts, alerts, and status-heavy surfaces instead of hard-coding raw color values inside components.

Tailwind v4 theme mapping

Add this @theme block so utilities like bg-primary and text-muted-foreground resolve to your variables.

app/globals.css
@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.

Code
/* 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.

app/layout.tsx
import { ThemeProvider } from "next-themes"

export default function RootLayout({ children }) {
  return (
    <html suppressHydrationWarning>
      <body>
        <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}
Code
pnpm add next-themes

Base UI data attributes

Base UI applies these attributes to components. Use them in Tailwind for state-based styling.

data-[open]Overlay or disclosure is open
data-[closed]Overlay is closed — use for exit animations
data-[checked]Checkbox, switch, or radio is checked
data-[indeterminate]Checkbox is indeterminate
data-[disabled]Component is disabled
data-[highlighted]List item is focused/highlighted
data-[selected]Select item is selected
data-[starting-style]Entry animation start state
data-[side=top|bottom|left|right]Floating element placement
Code
/* 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.

app/globals.css
@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;
  }
}