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:
| Layer | File | Purpose |
|---|---|---|
| CSS Variables | theme/variables.css | Runtime theming — change these to re-theme |
| JS Tokens | packages/tokens/*.js | Build-time definitions for Tailwind preset |
| Tailwind Preset | tailwind.preset.cjs | Maps tokens to Tailwind utility classes |
Recommended workflow
- Start with semantic color variables —
--primary,--background,--foreground,--border,--ring. - Pair each background token with its foreground token to ensure readable contrast.
- Update dark-mode values in the
.darkblock — not only the light values on:root. - Rebuild Tailwind output if you changed build-time JS tokens or the preset configuration.
- Test contrast, focus rings, hover states, disabled states, and key components (buttons, inputs, modals, cards, sidebars).
- 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:
| Background | Foreground | Used on |
|---|---|---|
| --background | --foreground | Page body |
| --card | --card-foreground | Card and panel surfaces |
| --popover | --popover-foreground | Dropdowns, popovers, tooltips |
| --primary | --primary-foreground | Primary buttons, active indicators |
| --secondary | --secondary-foreground | Secondary buttons, muted actions |
| --muted | --muted-foreground | Subtle backgrounds, helper text |
| --accent | --accent-foreground | Hovered items, highlighted rows |
| --success | --success-foreground | Success toasts, positive states |
| --warning | --warning-foreground | Warning alerts, caution states |
| --danger | --danger-foreground | Danger buttons, error states |
| --info | --info-foreground | Info 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
.darkand 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
Related
- 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