Skip to main content

Styling

Kuzenbo uses a token-first styling approach with Tailwind CSS and Tailwind Variants for predictable, customizable components.

Styling system overview

Kuzenbo’s styling is built on:
  • Semantic tokens - CSS variables for colors, spacing, and other design tokens
  • Tailwind Variants - Type-safe variant system for component styling
  • Base UI - Accessible primitive components with render composition
  • Class composition - Flexible class override patterns

Semantic color tokens

All Kuzenbo components use semantic color tokens instead of raw Tailwind palette classes:
// ✅ Good: Use semantic tokens
<div className="bg-background text-foreground border-border">
  Content
</div>

// ❌ Avoid: Raw palette classes
<div className="bg-slate-50 text-slate-900 border-slate-200">
  Content
</div>

Available semantic tokens

  • bg-background - Main app background
  • bg-foreground - Inverted background
  • bg-card - Card/panel background
  • bg-popover - Popover/dropdown background
  • bg-muted - Subtle background
  • bg-accent - Accent background
  • bg-primary - Primary brand color
  • bg-secondary - Secondary color
Semantic tokens automatically adapt to light and dark mode without any code changes.

Component variants

Kuzenbo uses Tailwind Variants for type-safe component styling. Here’s how the Button component defines variants:
import { tv } from "tailwind-variants";

const buttonVariants = tv({
  base: "inline-flex items-center justify-center rounded-md font-medium transition-colors",
  variants: {
    variant: {
      default: "bg-primary text-primary-foreground hover:bg-primary/90",
      outline: "border border-input bg-transparent hover:bg-accent",
      ghost: "hover:bg-accent hover:text-accent-foreground",
      destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
    },
    size: {
      xs: "h-6 px-2 text-xs",
      sm: "h-8 px-2.5 text-sm",
      md: "h-9 px-2.5 text-sm",
      lg: "h-10 px-3 text-base",
      xl: "h-11 px-4 text-base",
    },
  },
  defaultVariants: {
    variant: "default",
    size: "md",
  },
});

Size system

Kuzenbo uses a consistent size system across all components:

Size scale

All components support five size variants:
  • xs - Extra small (compact UIs, dense tables)
  • sm - Small (tight layouts, mobile-first)
  • md - Medium (default, balanced)
  • lg - Large (comfortable, accessible)
  • xl - Extra large (prominent CTAs, hero sections)

Using sizes

import { Button } from "@kuzenbo/core/ui/button";
import { Input } from "@kuzenbo/core/ui/input";
import { Card } from "@kuzenbo/core/ui/card";

// Component-level sizes
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

<Input size="sm" />
<Card size="lg" />

Global size provider

Set a default size for all components using KuzenboProvider:
import { KuzenboProvider } from "@kuzenbo/core/provider";

export default function App() {
  return (
    <KuzenboProvider defaultSize="lg">
      {/* All components use size="lg" by default */}
      <Button>Large by default</Button>
      <Input placeholder="Large input" />

      {/* Override with component prop */}
      <Button size="sm">Small override</Button>
    </KuzenboProvider>
  );
}
The size system follows a precedence order: component prop → context provider → component default → global default.

Class composition

Kuzenbo components accept a className prop for customization:
import { Button } from "@kuzenbo/core/ui/button";

<Button className="w-full shadow-lg">
  Custom Button
</Button>

Class merging

Kuzenbo uses tailwind-merge to intelligently merge classes:
// Component has: "bg-primary text-sm"
// You add: "bg-secondary text-base"
// Result: "bg-secondary text-base" (your classes win)

<Button className="bg-secondary text-base">
  Overridden
</Button>

Composition patterns

You can compose components with wrapper classes:
import { Card } from "@kuzenbo/core/ui/card";

<Card className="p-6">
  <h2 className="text-xl font-semibold">Title</h2>
  <p className="text-muted-foreground mt-2">Description</p>
</Card>

Baseline styles

The optional @kuzenbo/styles package provides recommended global CSS:
import "@kuzenbo/styles/recommended.css";
This includes:
  • Scrollbar styling - Firefox thin scrollbars and WebKit custom scrollbars
  • Focus rings - Consistent :focus-visible outlines using semantic tokens
  • Text selection - Custom selection colors using semantic tokens
  • Smooth scrolling - Respects prefers-reduced-motion
  • Scroll anchoring - Hash links work with sticky headers
  • Font smoothing - Antialiased text on macOS/iOS
The baseline styles are optional but recommended for a polished user experience.

Custom component defaults

Set default props for specific components using KuzenboProvider:
import { KuzenboProvider } from "@kuzenbo/core/provider";

export default function App() {
  return (
    <KuzenboProvider
      components={{
        Button: {
          defaultProps: {
            variant: "outline",
            size: "lg",
          },
        },
        Input: {
          defaultProps: {
            size: "lg",
          },
        },
      }}
    >
      {/* All Buttons are outline/lg by default */}
      <Button>Outlined Large</Button>

      {/* Override individual props */}
      <Button variant="default">Solid Large</Button>
    </KuzenboProvider>
  );
}

Cursor primitives

Use the cursor-clickable token for interactive elements instead of raw cursor-pointer:
// ✅ Good: Use cursor-clickable token
<div className="cursor-clickable" onClick={handleClick}>
  Interactive
</div>

// ❌ Avoid: Raw cursor-pointer
<div className="cursor-pointer" onClick={handleClick}>
  Interactive
</div>
The cursor-clickable token uses the --kb-cursor CSS variable for consistent interactive styling.

Responsive variants

Use Tailwind’s responsive modifiers with component classes:
<Button className="w-full md:w-auto" size="lg">
  Full width on mobile, auto on desktop
</Button>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <Card size="sm" className="md:p-6" />
  <Card size="sm" className="md:p-6" />
  <Card size="sm" className="md:p-6" />
</div>

Dark mode

Use the dark: modifier for dark mode styles:
<div className="bg-white dark:bg-slate-900 text-slate-900 dark:text-white">
  Light and dark mode
</div>
Prefer semantic tokens over manual dark: modifiers. Tokens automatically adapt to the theme.

Global radius control

Customize border radius globally using KuzenboProvider:
import { KuzenboProvider } from "@kuzenbo/core/provider";

export default function App() {
  return (
    <KuzenboProvider defaultRadius="0.5rem">
      {/* All components use this radius via --kb-radius variable */}
    </KuzenboProvider>
  );
}
The defaultRadius prop accepts:
  • Number (pixels): 8
  • String (CSS units): "0.5rem", "8px", "0"

Next steps

Components

Explore all available components.

Theme runtime

Deep dive into theme customization.

Styles baseline

Learn about global baseline styles.

Build docs developers (and LLMs) love