Theming Guide

Configure Byteion to match any brand identity through the token system.

Overview

Byteion's themeable values are exposed through semantic CSS variables and build-time tokens. Core surfaces, text, borders, focus rings, radius, spacing, shadows, and layering can all be adjusted from the token system. Change a variable and every component that references it updates automatically — no component markup changes needed.

This system is designed for white-label apps, client dashboards, SaaS products, admin templates, starter kits, and any project that needs a consistent, rebrandable UI layer.

Why this exists

Traditional theming requires maintaining multiple CSS files or writing complex preprocessor logic. Byteion's variable-based approach means a theme is a single CSS file with redefined variable values. The Tailwind utilities and components never change — they read the variables at render time.

Token layers

Byteion tokens exist in three layers, each serving a different purpose:

LayerFilePurpose
CSS Variablestheme/variables.cssRuntime theming — change these to re-theme
JS Tokenspackages/tokens/*.jsBuild-time definitions for Tailwind preset
Tailwind Presettailwind.preset.cjsMaps tokens to Tailwind utility classes
  1. Start with semantic color variables — --primary, --background, --foreground, --border, --ring.
  2. Pair each background token with its foreground token to ensure readable contrast.
  3. Update dark-mode values in the .dark block — not only the light values on :root.
  4. Rebuild Tailwind output if you changed build-time JS tokens or the preset configuration.
  5. Test contrast, focus rings, hover states, disabled states, and key components (buttons, inputs, modals, cards, sidebars).
  6. Repeat for every brand theme or client palette you maintain.

Foreground / background pairs

Most semantic tokens are designed in matching pairs. Whenever you change a background variable, you must also verify its paired foreground for WCAG contrast:

BackgroundForegroundUsed on
--background--foregroundPage body
--card--card-foregroundCard and panel surfaces
--popover--popover-foregroundDropdowns, popovers, tooltips
--primary--primary-foregroundPrimary buttons, active indicators
--secondary--secondary-foregroundSecondary buttons, muted actions
--muted--muted-foregroundSubtle backgrounds, helper text
--accent--accent-foregroundHovered items, highlighted rows
--success--success-foregroundSuccess toasts, positive states
--warning--warning-foregroundWarning alerts, caution states
--danger--danger-foregroundDanger buttons, error states
--info--info-foregroundInfo banners, neutral states

Changing the primary color

The fastest way to re-brand Byteion is to change the primary color. This single variable updates every button, link, focus ring, and active indicator across all components:

:root {
 /* HSL triplet only — no hsl() wrapper, no hex, no rgb() */
 --primary: 270 100% 50%;           /* Purple */
 --primary-foreground: 0 0% 100%;   /* White text — must contrast with --primary */
}

After changing variables.css, rebuild your Tailwind output. Every bg-primary, text-primary, ring-primary, and border-primary class renders in the new color. Alpha utilities like bg-primary/50 continue to work because the variable is an HSL triplet without the hsl() wrapper — Tailwind's <alpha-value> syntax requires this format.

Changing the full palette

For complete re-theming, update all semantic color variables including their foreground pairs:

:root {
 /* Surfaces */
 --background: 0 0% 100%;
 --foreground: 240 10% 3.9%;
 --card: 0 0% 100%;
 --card-foreground: 240 10% 3.9%;
 --popover: 0 0% 100%;
 --popover-foreground: 240 10% 3.9%;
 --muted: 240 4.8% 95.9%;
 --muted-foreground: 240 3.8% 43%;
 --accent: 240 4.8% 95.9%;
 --accent-foreground: 240 5.9% 10%;

 /* Brand */
 --primary: 270 100% 50%;
 --primary-foreground: 0 0% 100%;
 --secondary: 240 4.8% 95.9%;
 --secondary-foreground: 240 5.9% 10%;

 /* Feedback */
 --success: 160 84% 30%;
 --success-foreground: 0 0% 100%;
 --warning: 32 92% 36%;
 --warning-foreground: 0 0% 100%;
 --danger: 350 89% 47%;
 --danger-foreground: 0 0% 100%;
 --info: 189 94% 28%;
 --info-foreground: 0 0% 100%;

 /* Chrome */
 --border: 240 5.9% 90%;
 --ring: 270 100% 50%;
}

Dark mode

Byteion ships with a complete dark theme defined in the .dark block of variables.css. When you re-theme the light palette, always update the dark values to match — the .dark block overrides the same variable names with dark-appropriate HSL values:

.dark, [data-theme="dark"] {
 /* Surfaces — swapped for dark */
 --background: 240 10% 3.9%;
 --foreground: 0 0% 98%;
 --card: 240 6% 10%;
 --card-foreground: 0 0% 98%;
 --popover: 240 6% 10%;
 --popover-foreground: 0 0% 98%;
 --muted: 240 3.7% 15.9%;
 --muted-foreground: 240 5% 64.9%;
 --accent: 240 3.7% 15.9%;
 --accent-foreground: 0 0% 98%;

 /* Brand */
 --primary: 270 100% 50%;              /* Stays the same as light */
 --primary-foreground: 0 0% 100%;
 --secondary: 240 3.7% 15.9%;
 --secondary-foreground: 0 0% 98%;

 /* Chrome */
 --border: 240 3.7% 15.9%;
 --ring: 270 100% 50%;                 /* Stays the same as light */
}

Only color and surface variables are overridden. Shadows, motion, radius, and spacing remain identical across modes. Components using semantic tokens (bg-background, text-foreground) adapt without any class changes.

For persisted class toggling and system preference fallback, see the Dark Mode page.

Spacing and radius

Spacing and border-radius tokens control the physical feel of the interface. Components that consume the spacing token directly, or through mapped utility classes in the preset, pick up the new value after rebuild:

:root {
 /* Spacing — legacy aliases consumed by the tailwind preset */
 --spacing-sm: 0.5rem;  /* 8px — compact density */
 --spacing-md: 1rem;    /* 16px — default */
 --spacing-lg: 1.5rem;  /* 24px — spacious */

 /* Border radius — each targets specific component families */
 --radius-button: 0.5rem;   /* Buttons */
 --radius-input: 0.5rem;    /* Inputs, selects, textareas */
 --radius-badge: 9999px;    /* Badges, tags, pills */
 --radius-card: 0.75rem;    /* Cards, panels */
 --radius-panel: 1rem;      /* Sidebars, drawer panels */
 --radius-overlay: 1.25rem; /* Modals, dialogs, popovers */
}

The semantic radius tokens (--radius-button, --radius-card, etc.) are backed by the rounded-* utility classes in the Tailwind preset. Changing --radius-card from 0.75rem to 1rem updates every card's corners after rebuild.

Multiple brand themes

Use scoped [data-theme="<brand>"] selectors to support multiple brands in a single codebase. This is useful for white-label SaaS, client dashboards, theme previews, documentation demos, and selling pre-built templates:

/* Purple brand theme */
[data-theme="purple"] {
 --primary: 270 100% 50%;
 --primary-foreground: 0 0% 100%;
 --ring: 270 100% 50%;
}

/* Emerald brand theme */
[data-theme="emerald"] {
 --primary: 160 84% 39%;
 --primary-foreground: 0 0% 100%;
 --ring: 160 84% 39%;
}

Apply the theme by setting data-theme on <html> or a scoped container. Brand themes compose with .dark and [data-theme="dark"] for multi-tenant dark mode support.

Customization levels

Level 1: Variable override (recommended)

Copy variables.css into your project and modify values. This is the safest approach — you get the full token system with your brand values.

Level 2: Partial override

Override only the variables you need to change. The rest inherit Byteion's defaults:

:root {
 --primary: 270 100% 50%;
 --radius-card: 1rem;
}

Level 3: Tailwind extension

Add custom colors, spacing, or animations alongside Byteion's preset for app-specific additions:

// tailwind.config.js
const byteionPreset = require('@byteion/core/preset');

module.exports = {
 presets: [byteionPreset],
 theme: {
  extend: {
   colors: {
    brand: { 50: '#faf5ff', 500: '#8b5cf6', 900: '#4c1d95' },
   },
  },
 },
};

Use extension for true app-specific additions. Do not use it to bypass Byteion's semantic tokens for shared or themeable properties — prefer CSS variables so the value can be overridden per theme or per mode.

Theme testing checklist

Before shipping a theme, verify each of these:

  • Test both light and dark mode — toggle .dark and inspect every surface.
  • Test all key components: buttons, inputs, cards, tables, modals, dropdowns, sidebars, toasts.
  • Test interactive states: hover, focus, disabled, selected, loading, and destructive.
  • Verify contrast for every foreground/background pair (aim for WCAG AA 4.5:1).
  • Test bg-primary/50, text-foreground/80, and other alpha utilities — they require HSL variable format.
  • Test real dashboard screens, not only isolated component previews.
  • Run accessibility and visual regression checks where available.

Do and Don't

Do

  • Use HSL variables for all theme colors — no hex, no rgb, no hsl() wrapper
  • Update foreground tokens whenever you change a paired background token
  • Test both light and dark palettes before shipping
  • Use CSS variable overrides for brand-level changes
  • Use the Tailwind preset for token-to-class mappings

Don't

  • Use hex or RGB inside semantic CSS variables if alpha utilities are expected
  • Change only light mode and forget to update dark mode values
  • Hardcode brand colors in shared component markup
  • Use arbitrary Tailwind values (text-[#333], bg-[#eee]) for themeable properties
  • Override component markup before trying token-level changes
  • Colors — Semantic and functional color token system
  • Dark Mode — Class toggling, persistence, and system preference
  • CSS Variables — Complete variable reference
  • Design Tokens — Token system architecture and naming
  • Brand Themes — Pre-built brand theme examples

Unlock Premium Patterns

Get access to copy-paste ready Pro blocks, SaaS templates, and advanced component patterns.

Explore Pro Components