Customization

Tailwind CSS configuration, semantic color system, custom animations, and global CSS architecture

Kit uses Tailwind CSS 3.4 with a semantic color system built on CSS custom properties. Instead of hardcoded color values like bg-blue-500, the entire application uses theme-aware classes like bg-primary that automatically adapt to the active color theme and dark mode. This page covers the Tailwind configuration, how to extend it, and the global CSS architecture.
For switching between themes, see Color Themes & Dark Mode. For component styling patterns, see UI Components.

Tailwind Configuration

The Tailwind config maps every semantic color to its CSS custom property and defines custom fonts, animations, and layout utilities:
tailwind.config.js — Full Configuration
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: ['class'],
  content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
    '../../packages/ui/src/**/*.{js,ts,jsx,tsx}',
  ],
  theme: {
    container: {
      center: true,
      padding: '2rem',
      screens: {
        '2xl': '1400px',
      },
    },
    extend: {
      fontFamily: {
        sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
        mono: ['var(--font-jetbrains-mono)', 'ui-monospace', 'monospace'],
      },
      backgroundImage: {
        'dot-pattern':
          'radial-gradient(circle, hsl(var(--foreground) / 0.1) 1px, transparent 1px)',
        'gradient-brand':
          'linear-gradient(to right, hsl(var(--gradient-start)), hsl(var(--gradient-end)))',
      },
      backgroundSize: {
        'dot-pattern': '20px 20px',
      },
      borderRadius: {
        lg: 'var(--radius)',
        md: 'calc(var(--radius) - 2px)',
        sm: 'calc(var(--radius) - 4px)',
      },
      keyframes: {
        'ping-once': {
          '0%': { transform: 'scale(1)', opacity: '1' },
          '75%': { transform: 'scale(1.25)', opacity: '0.75' },
          '100%': { transform: 'scale(1)', opacity: '1' },
        },
        'bounce-once': {
          '0%': { transform: 'translateY(0)' },
          '25%': { transform: 'translateY(-20%)' },
          '50%': { transform: 'translateY(0)' },
          '75%': { transform: 'translateY(-10%)' },
          '100%': { transform: 'translateY(0)' },
        },
        'pulse-once': {
          '0%': { opacity: '0' },
          '50%': { opacity: '0.5' },
          '100%': { opacity: '0' },
        },
        ripple: {
          '0%': {
            transform: 'scale(0)',
            opacity: '0.5',
          },
          '100%': {
            transform: 'scale(4)',
            opacity: '0',
          },
        },
      },
      animation: {
        'ping-once': 'ping-once 0.6s cubic-bezier(0.4, 0, 0.6, 1)',
        'bounce-once': 'bounce-once 0.6s cubic-bezier(0.4, 0, 0.6, 1)',
        'pulse-once': 'pulse-once 0.6s cubic-bezier(0.4, 0, 0.6, 1)',
        ripple: 'ripple 0.6s linear',
      },
      colors: {
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        card: {
          DEFAULT: 'hsl(var(--card))',
          foreground: 'hsl(var(--card-foreground))',
        },
        popover: {
          DEFAULT: 'hsl(var(--popover))',
          foreground: 'hsl(var(--popover-foreground))',
        },
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))',
        },
        secondary: {
          DEFAULT: 'hsl(var(--secondary))',
          foreground: 'hsl(var(--secondary-foreground))',
        },
        muted: {
          DEFAULT: 'hsl(var(--muted))',
          foreground: 'hsl(var(--muted-foreground))',
        },
        accent: {
          DEFAULT: 'hsl(var(--accent))',
          foreground: 'hsl(var(--accent-foreground))',
        },
        destructive: {
          DEFAULT: 'hsl(var(--destructive))',
          foreground: 'hsl(var(--destructive-foreground))',
        },
        success: {
          DEFAULT: 'hsl(var(--success))',
          foreground: 'hsl(var(--success-foreground))',
        },
        warning: {
          DEFAULT: 'hsl(var(--warning))',
          foreground: 'hsl(var(--warning-foreground))',
        },
        info: {
          DEFAULT: 'hsl(var(--info))',
          foreground: 'hsl(var(--info-foreground))',
        },
        footer: {
          DEFAULT: 'hsl(var(--footer-background))',
          foreground: 'hsl(var(--footer-foreground))',
        },
        border: 'hsl(var(--border))',
        input: 'hsl(var(--input))',
        ring: 'hsl(var(--ring))',
        chart: {
          1: 'hsl(var(--chart-1))',
          2: 'hsl(var(--chart-2))',
          3: 'hsl(var(--chart-3))',
          4: 'hsl(var(--chart-4))',
          5: 'hsl(var(--chart-5))',
        },
      },
    },
  },
  plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],
}
Key configuration points:
  • darkMode: ['class'] — Dark mode toggles via .dark class (managed by next-themes), not by media query
  • content paths — Scans both src/ directories and packages/ui/ so shared components get Tailwind classes
  • container — Centered with 2rem padding and a 1400px max-width at the 2xl breakpoint
  • colors — Every color references a CSS variable via hsl(var(--name)), making all colors theme-aware
  • pluginstailwindcss-animate for animation utilities and @tailwindcss/typography for prose styling

Semantic Color System

The color system works through three layers:
Theme CSS file (default.css)          →  --primary: 221 83% 53%;
    |
Tailwind config (tailwind.config.js)  →  primary: 'hsl(var(--primary))'
    |
Your components                       →  className="bg-primary text-primary-foreground"
When you use bg-primary, Tailwind generates background-color: hsl(var(--primary)). The actual color value comes from whichever theme CSS file is active.

Opacity Modifiers

Because CSS variables store raw HSL values (not wrapped in hsl()), Tailwind's opacity modifier syntax works out of the box:
tsx
// 50% opacity primary color
<div className="bg-primary/50" />
// Generates: background-color: hsl(221 83% 53% / 0.5)

// 80% opacity foreground text
<p className="text-foreground/80" />
// Generates: color: hsl(0 0% 3.9% / 0.8)

Color Usage Reference

Tailwind ClassCSS VariableWhen to Use
bg-background--backgroundPage background
text-foreground--foregroundPrimary body text
bg-card--cardCard and surface backgrounds
bg-primary--primaryPrimary buttons, links, brand elements
text-primary-foreground--primary-foregroundText on primary-colored backgrounds
bg-secondary--secondarySecondary buttons, less prominent elements
bg-muted--mutedSubtle background areas, disabled states
text-muted-foreground--muted-foregroundHelp text, placeholders, timestamps
bg-accent--accentHover states, selected items
bg-destructive--destructiveError states, delete buttons
border-border--borderAll borders and dividers
border-input--inputForm input borders
ring-ring--ringFocus ring outlines
bg-gradient-brand--gradient-start/endBrand gradient backgrounds

Extending the Configuration

Custom Fonts

The config defines two font families using CSS variables set by next/font:
typescript
fontFamily: {
  sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
  mono: ['var(--font-jetbrains-mono)', 'ui-monospace', 'monospace'],
}
To change fonts, update the next/font imports in apps/boilerplate/src/app/layout.tsx and the variable names will flow through automatically. The font-sans class applies Inter and font-mono applies JetBrains Mono.

Background Patterns

Two custom background utilities are pre-configured:
typescript
backgroundImage: {
  'dot-pattern': 'radial-gradient(circle, hsl(var(--foreground) / 0.1) 1px, transparent 1px)',
  'gradient-brand': 'linear-gradient(to right, hsl(var(--gradient-start)), hsl(var(--gradient-end)))',
}
Use bg-dot-pattern for subtle decorative backgrounds and bg-gradient-brand for hero sections or accent areas. Both are theme-aware — dot-pattern uses the foreground color at 10% opacity, and gradient-brand uses the theme's gradient endpoints.

Border Radius

Border radius values are derived from the --radius CSS custom property (default: 0.5rem):
typescript
borderRadius: {
  lg: 'var(--radius)',          // 0.5rem
  md: 'calc(var(--radius) - 2px)', // ~0.375rem
  sm: 'calc(var(--radius) - 4px)', // ~0.25rem
}
Changing --radius in a theme CSS file adjusts all rounded corners throughout the application.

Custom Animations

Kit includes 4 custom animations for interactive feedback:
AnimationClassDurationUse Case
Ping Onceanimate-ping-once0.6sButton click feedback, notification pulse
Bounce Onceanimate-bounce-once0.6sSuccess confirmation, attention draw
Pulse Onceanimate-pulse-once0.6sFade-in highlight effect
Rippleanimate-ripple0.6sMaterial-style ripple on click
All animations use cubic-bezier(0.4, 0, 0.6, 1) easing (except ripple which uses linear) and run exactly once — they don't loop. This makes them ideal for interaction feedback where you want a single visual response.
To add a new animation, define a keyframes entry and matching animation entry in the extend section of tailwind.config.js:
javascript
keyframes: {
  'slide-in': {
    '0%': { transform: 'translateX(-100%)', opacity: '0' },
    '100%': { transform: 'translateX(0)', opacity: '1' },
  },
},
animation: {
  'slide-in': 'slide-in 0.3s ease-out',
},

Global CSS Architecture

The global stylesheet establishes the base styling for the entire application. It is structured in layers:
src/app/globals.css — Theme Imports, Tailwind Directives & Base Layer
/* Import all available color themes - MUST be first! */
@import '../styles/themes/default.css';
@import '../styles/themes/ocean.css';
@import '../styles/themes/forest.css';
@import '../styles/themes/sunset.css';
@import '../styles/themes/midnight.css';
@import '../styles/themes/coral.css';
@import '../styles/themes/slate.css';
@import '../styles/themes/aurora.css';
@import '../styles/themes/crimson.css';

/* Syntax highlighting (extracted for maintainability) */
@import '../styles/syntax-highlighting.css';

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }

  /* Typography System */
  h1 {
    @apply scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl;
  }
  h2 {
    @apply scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0;
  }
  h3 {
    @apply scroll-m-20 text-2xl font-semibold tracking-tight;
  }
  h4 {
    @apply scroll-m-20 text-xl font-semibold tracking-tight;
  }
  p {
    @apply leading-7 [&:not(:first-child)]:mt-6;
  }
  blockquote {
    @apply mt-6 border-l-2 pl-6 italic;
  }
  ul {
    @apply my-6 ml-6 list-disc [&>li]:mt-2;
  }
  code {
    @apply relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold;
  }
}
The file is organized in 4 distinct sections:
  1. Theme imports (lines 1–9) — All 9 theme CSS files must be imported first, before any Tailwind directives. This ensures CSS custom properties are available when Tailwind generates utility classes.
  2. Tailwind directives (lines 12–14) — @tailwind base, @tailwind components, @tailwind utilities inject Tailwind's generated styles at the right layer.
  3. Base layer (lines 16–49) — @layer base sets global defaults: border color on all elements, background/text color on body, and a full typography system (h1–h4, p, blockquote, ul, code) using semantic Tailwind classes.
  4. Components layer (further in file) — @layer components defines reusable patterns like code block styling for prose content with dark mode adjustments.

Adding Custom Utilities

CSS Utilities

Add custom utility classes using Tailwind's @layer utilities directive in globals.css:
css
@layer utilities {
  .text-balance {
    text-wrap: balance;
  }

  .scrollbar-hide {
    -ms-overflow-style: none;
    scrollbar-width: none;
  }
  .scrollbar-hide::-webkit-scrollbar {
    display: none;
  }
}

Extending Tailwind Config

For utilities that need responsive variants or other Tailwind modifiers, add them to the config:
javascript
// tailwind.config.js — extend section
extend: {
  spacing: {
    '18': '4.5rem',
    '88': '22rem',
  },
  maxWidth: {
    'prose-wide': '85ch',
  },
  zIndex: {
    '60': '60',
    '70': '70',
  },
}
Config-based extensions automatically get responsive (md:max-w-prose-wide), hover (hover:z-60), and other modifier support.

Key Files

FilePurpose
apps/boilerplate/tailwind.config.jsTailwind configuration — colors, fonts, animations, plugins
apps/boilerplate/src/app/globals.cssGlobal styles — theme imports, base layer, typography, custom utilities
apps/boilerplate/src/styles/themes/*.css9 theme CSS files with CSS custom properties (light + dark)
apps/boilerplate/postcss.config.jsPostCSS configuration for Tailwind and autoprefixer
packages/utils/src/index.tscn() utility — merges Tailwind classes with conflict resolution