Documentation

Theming

Learn how to customize the appearance of your directory website using Tailwind CSS, Radix UI, and shadcn/ui components.

Dirstarter uses a modern theming system built on top of Tailwind CSS v4, Radix UI primitives, and shadcn/ui components. This guide will help you understand and customize the styles of your website.

Why this theming system?

The combination of Tailwind CSS, Radix UI and shadcn/ui components allows ready-to-use, complex UI components that can be fully customized to match your brands design.

Color System

The color system is defined using CSS variables in the @theme configuration. The base theme includes:

app/styles.css
@theme {
  --color-background: hsl(0 0% 100%);
  --color-foreground: hsl(0 0% 12%);
  --color-border: hsl(0 0% 88%);
  --color-input: hsl(0 0% 88%);
  --color-ring: hsl(0 0% 83%);
  --color-card: hsl(0 0% 98%);
  --color-card-foreground: hsl(0 0% 12%);
  --color-popover: hsl(0 0% 100%);
  --color-popover-foreground: hsl(0 0% 3.9%);
  --color-primary: hsl(234 98% 61%);
  --color-primary-foreground: hsl(0 0% 98%);
  --color-secondary: hsl(0 0% 96%);
  --color-secondary-foreground: hsl(0 0% 30%);
  --color-muted: hsl(0 0% 96%);
  --color-muted-foreground: hsl(0 0% 45.1%);
  --color-accent: hsl(0 0% 96%);
  --color-accent-foreground: hsl(0 0% 9%);
  --color-destructive: hsl(0 84.2% 60.2%);
  --color-destructive-foreground: hsl(0 0% 98%);
}

Dark Mode

Dark mode is handled using the .dark class selector inside @layer base. The dark theme colors are defined as:

app/styles.css
@layer base {
  .dark {
    --color-background: hsl(0 0% 5%);
    --color-foreground: hsl(0 0% 90%);
    --color-border: hsl(0 0% 15%);
    --color-input: hsl(0 0% 15%);
    --color-ring: hsl(0 0% 20%);
    --color-card: hsl(0 0% 8%);
    --color-card-foreground: hsl(0 0% 90%);
    --color-popover: hsl(0 0% 5%);
    --color-popover-foreground: hsl(0 0% 90%);
    --color-primary: hsl(234 98% 61%);
    --color-primary-foreground: hsl(0 0% 98%);
    --color-secondary: hsl(0 0% 10%);
    --color-secondary-foreground: hsl(0 0% 70%);
    --color-muted: hsl(0 0% 10%);
    --color-muted-foreground: hsl(0 0% 60%);
    --color-accent: hsl(0 0% 10%);
    --color-accent-foreground: hsl(0 0% 90%);
    --color-destructive: hsl(0 62.8% 30.6%);
    --color-destructive-foreground: hsl(0 0% 98%);
  }
}

Typography

The typography system uses Geist font as the primary font family by default:

app/styles.css
@theme {
  --font-system:
    -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",
    Helvetica, Arial, sans-serif, "Apple Color Emoji",
    "Segoe UI Emoji";
}

The --font-sans variable is set by the Geist font loader in lib/fonts.ts and applied to the root layout automatically by Next.js:

lib/fonts.ts
export const fontSans = Geist({
  variable: "--font-sans",
  display: "swap",
  subsets: ["latin"],
  weight: "variable",
})

Component Variants

Components use the cva.style utility for variant management. This is a modern and powerful utility for defining and managing class variants. Here's an example of how variants are defined:

components/common/button.tsx
const buttonVariants = cva({
  base: [
    "group/button inline-flex items-center justify-center",
    "border-transparent! font-medium text-[0.8125rem]/tight",
    "text-start rounded-md overflow-clip hover:z-10",
    "hover:border-transparent",
    "disabled:opacity-60 disabled:cursor-not-allowed",
  ],
  variants: {
    variant: {
      fancy:
        "scheme-dark bg-primary text-primary-foreground hover:opacity-90",
      primary:
        "scheme-dark text-background bg-foreground hover:opacity-90",
      secondary:
        "scheme-light border-border! bg-background text-secondary-foreground"
        + " hover:bg-card hover:border-ring!",
      soft:
        "scheme-light bg-muted text-secondary-foreground"
        + " hover:bg-border/50 hover:text-foreground hover:outline-none",
      ghost:
        "scheme-light text-secondary-foreground"
        + " hover:bg-muted hover:text-foreground hover:outline-none",
      destructive:
        "scheme-light bg-destructive text-destructive-foreground"
        + " hover:bg-destructive/90",
    },
    size: {
      sm: "px-2 py-1 gap-[0.66ch]",
      md: "px-3 py-2 gap-[0.75ch]",
      lg: "px-4 py-2.5 gap-[1ch] rounded-lg sm:text-sm/tight",
    },
  },
  defaultVariants: {
    variant: "primary",
    size: "lg",
  },
})

Customizing Components

Using Radix UI Primitives

Components are built on top of Radix UI primitives for accessibility and functionality. Components re-export Radix primitives with sensible defaults — see components/common/ for examples.

Styling with Tailwind

Components use Tailwind CSS for styling. You can customize the appearance by:

  1. Modifying the base styles in the component's cva configuration
  2. Extending the component with additional classes using the className prop
  3. Overriding the theme variables in your CSS

Example: Custom Button

const CustomButton = ({ className, ...props }) => (
  <button
    className={cx(buttonVariants({ variant: "primary", size: "lg" }), "custom-styles", className)}
    {...props}
  />
)

Best Practices

  1. Use CSS Variables: Always use theme variables for colors and other design tokens
  2. Maintain Consistency: Follow the established patterns for component variants
  3. Accessibility: Leverage Radix UI primitives for accessible components
  4. Responsive Design: Use the responsive utilities provided by Tailwind
  5. Dark Mode: Test your customizations in both light and dark modes

Extending the Theme

To extend the theme:

  1. Add new CSS variables in the @theme configuration
  2. Create new component variants using cva
  3. Add new utility classes in the Tailwind configuration
  4. Create new components following the established patterns

Last updated on

On this page

Join hundreds of directory builders

Build your directory, launch, earn

Don't waste time on Stripe subscriptions or designing a pricing section. Get started today with our battle-tested stack and built-in monetization features.

Get Lifetime Access