Skip to content

Theming ​

Basekit Laravel UI uses Tailwind CSS 4's CSS-based theming system for maximum flexibility and runtime customization.

CSS Variables Architecture ​

The theming system uses a three-tier variable architecture:

  1. Global Design Tokens - Brand colors, spacing, typography
  2. Component Tokens - Component-specific values
  3. Instance Variables - Per-component inline overrides

Global Design Tokens ​

Override global tokens to customize your entire design system:

css
/* resources/css/app.css */
:root {
  /* Primary Brand Color */
  --color-primary-600: #7c3aed;
  --color-primary-700: #6d28d9;

  /* Semantic Surfaces */
  --surface-base: #ffffff;

  /* Border Radius */
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;

  /* Spacing */
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;

  /* Typography */
  --font-weight-medium: 600;
}

Component-Level Tokens ​

Customize specific components without affecting others:

css
:root {
  /* Shared Surface */
  --surface-base: #ffffff;

  /* Button Component */
  --button-bg-primary: #7c3aed;
  --button-text-primary: white;
  --button-radius: 0.5rem;
  --button-padding-x-md: 1.25rem;

  /* Input Component */
  --input-border-color: #e5e7eb;
  --input-focus-ring: #7c3aed;

  /* Card Component */
  --card-bg: white;
  --card-border-color: #e5e7eb;
  --card-padding: 1.5rem;
  --card-radius: 0.75rem;
}

Complete CSS Variable Reference ​

Global Design Tokens ​

These tokens are defined once and referenced by all components. Override any of them to apply changes globally.

Primary Color ​

css
:root {
  --color-primary-50: #eef2ff;
  --color-primary-100: #e0e7ff;
  --color-primary-200: #c7d2fe;
  --color-primary-300: #a5b4fc;
  --color-primary-400: #818cf8;
  --color-primary-500: #6366f1;
  --color-primary-600: #4f46e5;
  --color-primary-700: #4338ca;
  --color-primary-800: #3730a3;
  --color-primary-900: #312e81;
  --color-primary-950: #1e1b4b;
}

Slate (neutral / secondary) Color ​

css
:root {
  --color-slate-50: #f8fafc;
  --color-slate-100: #f1f5f9;
  --color-slate-200: #e2e8f0;
  --color-slate-300: #cbd5e1;
  --color-slate-400: #94a3b8;
  --color-slate-500: #64748b;
  --color-slate-600: #475569;
  --color-slate-700: #334155;
  --color-slate-800: #1e293b;
  --color-slate-900: #0f172a;
  --color-slate-950: #020617;
}

Success Color ​

css
:root {
  --color-success-50: #f0fdf4;
  --color-success-100: #dcfce7;
  --color-success-200: #bbf7d0;
  --color-success-300: #86efac;
  --color-success-400: #4ade80;
  --color-success-500: #22c55e;
  --color-success-600: #16a34a;
  --color-success-700: #15803d;
  --color-success-800: #166534;
  --color-success-900: #14532d;
  --color-success-950: #052e16;
}

Warning Color ​

css
:root {
  --color-warning-50: #fffbeb;
  --color-warning-100: #fef3c7;
  --color-warning-200: #fde68a;
  --color-warning-300: #fcd34d;
  --color-warning-400: #fbbf24;
  --color-warning-500: #f59e0b;
  --color-warning-600: #d97706;
  --color-warning-700: #b45309;
  --color-warning-800: #92400e;
  --color-warning-900: #78350f;
  --color-warning-950: #451a03;
}

Danger Color ​

css
:root {
  --color-danger-50: #fef2f2;
  --color-danger-100: #fee2e2;
  --color-danger-200: #fecaca;
  --color-danger-300: #fca5a5;
  --color-danger-400: #f87171;
  --color-danger-500: #ef4444;
  --color-danger-600: #dc2626;
  --color-danger-700: #b91c1c;
  --color-danger-800: #991b1b;
  --color-danger-900: #7f1d1d;
  --color-danger-950: #450a0a;
}

Info Color ​

css
:root {
  --color-info-50: #eff6ff;
  --color-info-100: #dbeafe;
  --color-info-200: #bfdbfe;
  --color-info-300: #93c5fd;
  --color-info-400: #60a5fa;
  --color-info-500: #3b82f6;
  --color-info-600: #2563eb;
  --color-info-700: #1d4ed8;
  --color-info-800: #1e40af;
  --color-info-900: #1e3a8a;
  --color-info-950: #172554;
}

Border Radius ​

css
:root {
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-xl: 1rem;
  --radius-2xl: 1.5rem;
  --radius-full: 9999px;
}

Spacing ​

css
:root {
  --spacing-xs: 0.5rem;
  --spacing-sm: 0.75rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  --spacing-2xl: 3rem;
}

Font Sizes ​

css
:root {
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-md: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;
  --font-size-2xl: 1.5rem;
}

Font Weights ​

css
:root {
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
}

Shadows ​

css
:root {
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  --shadow-lg:
    0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
  --shadow-xl:
    0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}

Transitions ​

css
:root {
  --transition-fast: 150ms;
  --transition-normal: 200ms;
  --transition-slow: 300ms;
}

Component Tokens (Examples) ​

These variables are component-scoped. They are not global design tokens, but they often reference global tokens such as colors, radius, spacing, and transitions.

Button Variables ​

css
:root {
  /* Sizing */
  --button-padding-x-sm: 0.75rem;
  --button-padding-y-sm: 0.375rem;
  --button-font-size-sm: 0.875rem;

  --button-padding-x-md: 1rem;
  --button-padding-y-md: 0.5rem;
  --button-font-size-md: 1rem;

  --button-padding-x-lg: 1.25rem;
  --button-padding-y-lg: 0.625rem;
  --button-font-size-lg: 1.125rem;

  /* Variants */
  --button-bg-primary: var(--color-primary-600);
  --button-text-primary: white;
  --button-hover-bg-primary: var(--color-primary-700);
  --button-active-bg-primary: var(--color-primary-800);
  --button-focus-ring-primary: var(--color-primary-500);

  /* Repeat for other variants */

  /* General */
  --button-radius: var(--radius-md);
  --button-font-weight: var(--font-weight-medium);
  --button-transition: var(--transition-normal);
}

Input Variables ​

css
:root {
  --input-border-color: #e5e7eb;
  --input-focus-ring: var(--color-primary-500);
  --input-error-border: var(--color-danger-500);
  --input-error-ring: var(--color-danger-500);

  /* Sizing */
  --input-padding-x-sm: 0.75rem;
  --input-padding-y-sm: 0.375rem;
  --input-font-size-sm: 0.875rem;
  /* ... md, lg */
}

Card Variables ​

css
:root {
  --card-bg: white;
  --card-border-color: #e5e7eb;
  --card-padding: 1.5rem;
  --card-radius: 0.5rem;
  --card-shadow: var(--shadow-md);
}

Dark Mode ​

All components ship with built-in dark mode overrides via a parent .dark class — no manual CSS required. Most components have dark overrides; purely structural components (Stack, Grid, Container, Divider) contain no color tokens and are unaffected by dark mode.

Enabling Dark Mode ​

Add the .dark class to a parent element — typically <html> or <body> — and all Basekit components inside it will automatically switch to dark-appropriate colors. dark: Tailwind utilities also work alongside the component CSS, and native browser controls (scrollbars, form elements) follow the theme via color-scheme:

blade
<html class="{{ $darkMode ? 'dark' : '' }}">

You can also scope dark mode to a specific section:

blade
<div class="dark">
    <x-basekit-ui::card>
        This card will render in dark mode
    </x-basekit-ui::card>
</div>

How It Works ​

The package registers a Tailwind CSS @custom-variant dark directive in theme.css so that dark: Tailwind utilities (e.g., dark:bg-gray-900) work alongside the component CSS variable system:

css
@custom-variant dark (&:where(.dark, .dark *));

Each component's CSS file contains a .dark {} block that re-declares its design tokens with dark-appropriate values. When the .dark class is present on any ancestor, CSS specificity causes the dark overrides to take effect automatically.

A global --surface-base token is also overridden under .dark so that components referencing it (Card, Input, Table, etc.) get a consistent dark background without individual overrides.

Native browser controls (scrollbars, <select> dropdowns, date pickers) follow the theme via color-scheme:

css
:root { color-scheme: light; }
.dark { color-scheme: dark; }

All overrides reference Tailwind CSS color tokens (e.g., var(--color-slate-800), var(--color-primary-400)) via a shared theme.css file, so dark mode stays in sync with your Tailwind palette.

Color Mapping ​

The built-in overrides follow a consistent light-to-dark mapping:

Light ValueDark ReplacementUsed For
--surface-base (white)slate-900Global surface background; component-specific overrides may use slate-800
slate-50 / slate-100slate-800 / slate-700Backgrounds, hover states
slate-200 / slate-300slate-700 / slate-600Borders, dividers
slate-400 / slate-500slate-400 / slate-500Placeholders, icons (unchanged)
slate-600slate-300 / slate-400Muted text
slate-700 / slate-900slate-200 / slate-100Body text, headings
{variant}-50{variant}-950Variant backgrounds (e.g., badge, alert, toast)
{variant}-200{variant}-200Variant text (unchanged)
{variant}-500{variant}-500Variant borders (unchanged)
{variant}-600 / {variant}-700{variant}-400Variant text on dark
White thumb / ringslate-300 / slate-900Toggle thumb, focus rings
rgba(0,0,0,0.5)rgba(0,0,0,0.7)Overlays (higher opacity)

Per-Component Dark Mode Tokens ​

Every component with color tokens has a complete set of dark overrides. Below is a summary of the key tokens per component that change in dark mode. Refer to each component's CSS file for the full list.

Form Components ​

ComponentKey Dark Tokens
Input--input-bg, --input-color, --input-border-color, --input-label-color, --input-hint-color, --input-addon-bg, --input-disabled-bg, plus all variant borders/rings (35 dark variables total)
Select--select-bg, --select-color, --select-border-color, --select-label-color, --select-option-text, --select-option-selected-bg, --select-menu-bg (36 dark variables total)
Textarea--textarea-bg, --textarea-text, --textarea-border, --textarea-label-color, --textarea-hint-color, --textarea-disabled-bg (26 dark variables total)
MultiSelect--multiselect-bg, --multiselect-color, --multiselect-chip-bg, --multiselect-chip-text, --multiselect-option-text, --multiselect-menu-bg, --multiselect-label-color (43 dark variables total)
Checkbox--checkbox-border-color, --checkbox-label-color, --checkbox-hint-color, --checkbox-disabled-bg, plus all variant checked/focus colors (33 dark variables total)
Radio--radio-border, --radio-bg, --radio-label-color, --radio-hint-color, --radio-disabled-bg, plus all variant checked/focus colors (30 dark variables total)
Toggle--toggle-bg-off, --toggle-thumb-bg, --toggle-thumb-border-off, --toggle-label-color, --toggle-hint-color, --toggle-disabled-label-color (23 dark variables total)
Button--button-shadow, --button-shadow-hover, --button-bg-secondary, --button-text-secondary, --button-border-secondary, --button-hover-bg-secondary, --button-text-ghost (33 dark variables total)

Display Components ​

ComponentKey Dark Tokens
Card--card-bg, --card-border, --card-footer-bg, --card-shadow, --card-header-text (5 dark variables)
Table--table-bg, --table-border, --table-header-bg, --table-header-text, --table-body-text, --table-row-hover-bg, --table-stripe-bg, --table-menu-bg, --table-empty-text, --table-expand-color (23 dark variables total)
Badge--badge-bg-secondary, --badge-text-secondary, --badge-border-secondary, plus all variant bg/text/border pairs (19 dark variables total)
Stat--stat-bg, --stat-label-color, --stat-value-color, --stat-icon-bg, --stat-icon-color, --stat-change-up-color, --stat-change-down-color (8 dark variables)
Avatar--avatar-bg, --avatar-text, --avatar-ring, --avatar-status-border, plus status colors (8 dark variables)
DescriptionList--dl-term-color, --dl-description-color, --dl-border, --dl-striped-bg (4 dark variables)
List--list-divided-border (1 dark variable)

Feedback Components ​

ComponentKey Dark Tokens
Alert--alert-bg-primary/info/secondary/success/warning/danger, --alert-text-*, --alert-border-* (19 dark variables total)
Toast--toast-shadow, --toast-bg-*, --toast-text-*, --toast-border-*, --toast-dismiss-hover-bg (21 dark variables total)
Tooltip--tooltip-bg, --tooltip-text, --tooltip-shadow (3 dark variables)
Progress--progress-bg, --progress-label-color, --progress-bar-primary/secondary/success/warning/danger/info/ghost/white (10 dark variables)
Spinner--spinner-color-primary/slate/success/warning/danger/info/ghost (7 dark variables)
EmptyState--empty-state-icon-color, --empty-state-title-color, --empty-state-description-color, plus all variant icon/title/description colors (24 dark variables total)
Skeleton--skeleton-bg (1 dark variable)
ComponentKey Dark Tokens
Tabs--tabs-border, --tabs-active-border, --tabs-text, --tabs-active-text, --tabs-hover-text, --tabs-pills-active-bg, --tabs-boxed-bg, --tabs-boxed-active-bg (9 dark variables)
Pagination--pagination-bg, --pagination-border, --pagination-text, --pagination-hover-bg, --pagination-active-bg, --pagination-active-text, --pagination-active-hover-bg, --pagination-disabled-text, --pagination-info-color, --pagination-label-color (10 dark variables)
DropdownMenu--dropdown-bg, --dropdown-trigger-bg, --dropdown-trigger-text, --dropdown-trigger-hover-bg, --dropdown-trigger-hover-border, --dropdown-border, --dropdown-shadow, --dropdown-item-hover-bg, --dropdown-item-text, --dropdown-item-icon (12 dark variables)
Link--link-primary-color, --link-secondary-color, --link-success-color, --link-warning-color, --link-danger-color, --link-info-color, --link-ghost-color, --link-muted-color (8 dark variables)
Breadcrumb--breadcrumb-separator-color, --breadcrumb-link-color, --breadcrumb-current-color (3 dark variables)

Dialog Components ​

ComponentKey Dark Tokens
Modal--modal-overlay-bg, --modal-bg, --modal-shadow, --modal-header-border, --modal-footer-border, --modal-close-color, --modal-close-hover-bg, --modal-close-hover-color, --modal-title-color (9 dark variables)
Accordion--accordion-border, --accordion-header-bg, --accordion-hover-bg, --accordion-title-color, --accordion-icon-color, --accordion-body-color, --accordion-flush-hover-color (7 dark variables)

Layout Components ​

ComponentKey Dark Tokens
Divider--divider-color, --divider-color-light, --divider-color-dark, --divider-label-color (4 dark variables)

Total: 467 dark mode CSS variables across 30 components.

Custom Dark Mode Overrides ​

Override any component's dark mode tokens by re-declaring them under .dark in your theme file:

css
.dark {
  --card-bg: var(--color-slate-900);
  --card-border: var(--color-slate-600);
  --input-bg: #0f172a;
  --input-border-color: #334155;
}

You can also scope overrides to a specific component instance:

blade
<x-basekit-ui::card style="--card-bg: #0f172a; --card-border: #334155;">
    Dark card with custom colors
</x-basekit-ui::card>

Auto Dark Mode with Media Query ​

The package ships with @custom-variant dark (&:where(.dark, .dark *)) which makes dark mode class-based. If you prefer automatic dark mode based on the user's OS preference instead, replace the variant in your CSS:

css
@import "tailwindcss";
@custom-variant dark (@media (prefers-color-scheme: dark));

This removes the need for a .dark toggle — dark styles apply automatically when the OS is in dark mode. If you want both (class-based toggle with OS fallback), combine them:

css
@custom-variant dark {
  &:where(.dark, .dark *) {
    @slot;
  }
  @media (prefers-color-scheme: dark) {
    &:where(:not(.light *)) {
      @slot;
    }
  }
}

This applies dark styles when .dark is present, or when the OS prefers dark and no .light class is set.

Components Without Dark Overrides ​

These components have no dark mode overrides because they contain no color tokens:

  • Stack — layout spacing only (gap size tokens)
  • Grid — layout columns only (no CSS custom properties)
  • Media — only contains @custom-media breakpoint definitions

Per-Component Scoping ​

Override variables for specific component instances:

blade
<x-basekit-ui::button
    variant="primary"
    style="--button-bg-primary: #7c3aed; --button-radius: 1rem;"
>
    Custom Styled Button
</x-basekit-ui::button>

Custom Colors via Props ​

All color-using components expose dedicated props as a higher-level alternative to inline CSS variables:

  • color — Quick shortcut that auto-sets background, text, border, hover, focus, and active states simultaneously.
  • background / text / border — Granular control over individual surfaces.
  • hover-background / hover-text / hover-border — Hover state overrides.

The color prop accepts Tailwind v4 color names (e.g., indigo-500, pink-200) or any raw CSS value (hex, #4F46E5; rgb, rgb(79,70,229); hsl, hsl(245,58%,59%); named colors).

When you pass a Tailwind color name, the component automatically:

  • Darkens the shade by 100 for hover states (e.g., indigo-500 → indigo-600)
  • Darkens the shade by 200 for active and border states (e.g., indigo-500 → indigo-700)
  • Picks a contrasting text color (white on shades ≥500, dark on shades ≤400)
  • Derives a focus ring from the text shade (light on dark backgrounds, translucent on hex values)

Light-background components (badge, alert) use a lighter expansion:

  • Background: shade-50
  • Text: shade-700
  • Border: shade-200

This matches their built-in variant appearance (light tint, strong text).

Raw CSS values passthrough literally — no auto-hover or auto-contrast is applied.

blade
{{-- Quick shortcut --}}
<x-basekit-ui::button color="indigo-500">Indigo Button</x-basekit-ui::button>

{{-- Granular overrides --}}
<x-basekit-ui::alert
    background="#FEE2E2"
    text="#991B1B"
    border="#FECACA"
>
    Custom Red Alert
</x-basekit-ui::alert>

{{-- Hover overrides --}}
<x-basekit-ui::button
    color="emerald-500"
    hover-background="emerald-400"
>
    Lightened Hover
</x-basekit-ui::button>

Supported components: Button, Input, Textarea, Select, MultiSelect, Checkbox, Radio, Toggle, Alert, Toast, Spinner, Progress, Badge, Link, EmptyState — each exports only the props relevant to its visual model (e.g., Input exposes only border and hover-border; Spinner exposes only color and text).

Example: Complete Custom Theme ​

css
/* resources/css/custom-theme.css */

@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

/* Component imports from the build */
@import "../../public/vendor/basekit-laravel/v1/basekit-ui.css";

:root {
  color-scheme: light;

  /* Brand Colors */
  --color-primary-600: #7c3aed;
  --color-primary-700: #6d28d9;
  --color-primary-800: #5b21b6;

  /* Component Customization */
  --button-radius: 0.75rem;
  --button-font-weight: 600;
  --button-bg-primary: var(--color-primary-600);

  --card-radius: 1rem;
  --card-padding: 2rem;
  --card-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1);

  --input-radius: 0.5rem;
  --input-border-color: #d1d5db;

  --badge-radius: 9999px;

  /* Typography */
  --font-weight-medium: 600;
  --font-weight-semibold: 700;
}

/* Dark Mode — class-based (add .dark to <html>) */
.dark {
  color-scheme: dark;

  --surface-base: var(--color-slate-900);

  --card-bg: #0f172a;
  --card-header-text: #f1f5f9;
  --card-border: #1e293b;
  --card-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4);

  --input-bg: #0f172a;
  --input-color: #f1f5f9;
  --input-border-color: #334155;
  --input-label-color: #cbd5e1;
  --input-hint-color: #64748b;

  --button-bg-secondary: #1e293b;
  --button-text-secondary: #e2e8f0;
  --button-border-secondary: #475569;

  --table-bg: #0f172a;
  --table-border: #1e293b;
  --table-header-text: #e2e8f0;
  --table-body-text: #e2e8f0;
}

Theming Best Practices ​

  1. Use semantic tokens — Reference design tokens (var(--color-primary-600)) in component tokens instead of hardcoded values
  2. Scope appropriately — Use :root for brand-wide changes, component tokens for specifics
  3. Test dark mode — Verify all overrides work in both light and dark themes; use the styleguide to preview
  4. Document overrides — Keep track of customized variables in your theme file
  5. Version control — Commit your theme file with your application code
  6. Start with built-in defaults — Override only what you need; the 467 built-in dark mode variables handle most cases
  7. Set color-scheme — Always set color-scheme: light on :root and color-scheme: dark under .dark so native browser controls follow the theme

Build CSS with Artisan ​

Publish config and build the generated CSS from your app settings:

bash
php artisan basekit:ui:build

Then import the built CSS file:

css
/* resources/css/app.css */
@import "../../public/vendor/basekit-laravel/v1/basekit-ui.css";

And put your theme overrides after the import:

css
:root {
  --color-primary-600: #7c3aed;
  --card-radius: 1rem;
}

Migration Between Versions ​

When upgrading to a new major version, use the migration command:

bash
php artisan basekit:ui:migrate-theme --from=v1 --to=v2

This will:

  • Update CSS variable names if changed
  • Add new required variables
  • Warn about removed variables
  • Create a backup of your current theme

Next Steps ​

Released under the MIT License.