Themes
Documentation Tailwind CSS v4
SmartMaker uses TailwindCSS v4 for styling. This version introduces a new CSS-first syntax with @theme and @layer.
TailwindCSS 4 Configuration
Installation
TailwindCSS 4 is integrated via the Vite plugin:
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [
react(),
tailwindcss(),
],
});
CSS Entry Point
/* src/assets/styles/style.css */
@import "tailwindcss";
@layer theme, base, components;
@import "./theme.css" layer(theme);
@import "./base.css" layer(base);
Define a Theme
CSS Variables with @theme
/* src/assets/styles/theme.css */
@theme {
/* Primary colors */
--color-primary: #5fbabf;
--color-primary-light: #8cd4d8;
--color-primary-dark: #4a9599;
--color-secondary: #fc8c8c;
--color-secondary-light: #fdb5b5;
--color-secondary-dark: #e67070;
/* Semantic colors */
--color-success: #22c55e;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #3b82f6;
/* Background colors */
--color-background: #ffffff;
--color-surface: #f8fafc;
--color-muted: #f1f5f9;
/* Text colors */
--color-foreground: #0f172a;
--color-foreground-muted: #64748b;
/* Custom spacing */
--spacing-page: 1rem;
--spacing-card: 1.5rem;
/* Border radii */
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
/* Typography */
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'Fira Code', monospace;
}
Usage in Components
The @theme variables automatically generate Tailwind classes:
<div className="bg-primary text-white">
Primary background
</div>
<div className="text-foreground-muted">
Muted text
</div>
<div className="rounded-lg shadow-md p-card">
Card with spacing
</div>
Base Styles
Reset and Global Rules
/* src/assets/styles/base.css */
@layer base {
* {
@apply box-border p-0 m-0;
-webkit-tap-highlight-color: transparent;
}
*::-webkit-scrollbar {
display: none;
}
html {
@apply scroll-smooth antialiased;
}
body {
@apply bg-background text-foreground font-sans;
}
/* Focus visible for accessibility */
:focus-visible {
@apply outline-2 outline-offset-2 outline-primary;
}
/* Links */
a {
@apply text-primary hover:text-primary-dark transition-colors;
}
/* Inputs */
input, textarea, select {
@apply bg-surface border border-gray-200 rounded-md;
@apply focus:border-primary focus:ring-1 focus:ring-primary;
}
}
Create Multiple Themes
File Structure
src/assets/
├── styles/
│ ├── style.css # Entry point
│ ├── theme.css # Default variables
│ └── base.css # Global rules
└── themes/
├── light.css # Light theme
└── dark.css # Dark theme
Light Theme (default)
/* src/assets/themes/light.css */
@theme {
--color-background: #ffffff;
--color-surface: #f8fafc;
--color-foreground: #0f172a;
--color-foreground-muted: #64748b;
}
Dark Theme
/* src/assets/themes/dark.css */
@theme {
--color-background: #0f172a;
--color-surface: #1e293b;
--color-foreground: #f8fafc;
--color-foreground-muted: #94a3b8;
}
Dynamic Loading
// src/main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./assets/styles/style.css";
// Load theme from settings
const theme = localStorage.getItem('theme') || 'light';
import(`./assets/themes/${theme}.css`);
import { App } from "./App";
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);
useTheme Hook
import { useGlobalStates } from '@cap-rel/smartcommon';
import { useEffect } from 'react';
export const useTheme = () => {
const [theme, setTheme] = useGlobalStates('settings.theme');
useEffect(() => {
// Apply theme to document
document.documentElement.setAttribute('data-theme', theme);
// Load theme CSS
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `/themes/${theme}.css`;
document.head.appendChild(link);
return () => {
document.head.removeChild(link);
};
}, [theme]);
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return { theme, setTheme, toggleTheme };
};
ThemeToggle Component
import { useTheme } from '../hooks/useTheme';
const ThemeToggle = () => {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
className="p-2 rounded-full bg-surface"
>
{theme === 'light' ? '🌙' : '☀️'}
</button>
);
};
Component Variants
With SmartCommon
SmartCommon allows defining variants for components:
// appConfig.js
export const config = {
components: {
variants: {
Button: {
default: "bg-primary text-white rounded-md px-4 py-2",
secondary: "bg-secondary text-white rounded-md px-4 py-2",
outline: "border-2 border-primary text-primary rounded-md px-4 py-2",
ghost: "text-primary hover:bg-primary/10 rounded-md px-4 py-2",
},
Input: {
default: "bg-surface border border-gray-200 rounded-md p-3",
filled: "bg-muted border-0 rounded-md p-3",
underline: "border-b-2 border-gray-200 rounded-none p-3",
},
},
},
};
Usage
import { Button, Input } from '@cap-rel/smartcommon';
const MyForm = () => {
return (
<form>
<Input name="email" variant="filled" />
<Button variant="secondary">Submit</Button>
<Button variant="ghost">Cancel</Button>
</form>
);
};
Custom Utility Classes
With @layer components
/* src/assets/styles/components.css */
@layer components {
.card {
@apply bg-surface rounded-lg shadow-md p-card;
}
.btn {
@apply inline-flex items-center justify-center;
@apply px-4 py-2 rounded-md font-medium;
@apply transition-all duration-200;
@apply focus:outline-none focus:ring-2 focus:ring-offset-2;
}
.btn-primary {
@apply btn bg-primary text-white;
@apply hover:bg-primary-dark;
@apply focus:ring-primary;
}
.btn-secondary {
@apply btn bg-secondary text-white;
@apply hover:bg-secondary-dark;
@apply focus:ring-secondary;
}
.input {
@apply w-full px-4 py-3 rounded-md;
@apply bg-surface border border-gray-200;
@apply focus:border-primary focus:ring-1 focus:ring-primary;
@apply placeholder:text-foreground-muted;
}
}
Automatic Dark Mode
With prefers-color-scheme
/* src/assets/styles/theme.css */
@theme {
--color-background: #ffffff;
--color-foreground: #0f172a;
}
@media (prefers-color-scheme: dark) {
@theme {
--color-background: #0f172a;
--color-foreground: #f8fafc;
}
}
With CSS Class
/* Default theme (light) */
@theme {
--color-background: #ffffff;
--color-foreground: #0f172a;
}
/* Dark theme via class */
.dark {
--color-background: #0f172a;
--color-foreground: #f8fafc;
}
// Toggle dark mode
document.documentElement.classList.toggle('dark');
Tips
Performance
- Use
@themefor reused variables - Avoid dynamic Tailwind classes (
bg-${color}) - Prefer component variants
Organization
- One file for variables (
theme.css) - One file for base rules (
base.css) - One file per additional theme
Accessibility
- Respect WCAG contrast ratios
- Test with
prefers-reduced-motion - Use
focus-visiblefor focus
See Also
- Configuration - Component variants
- SmartCommon - Available components
- Animations - Page transitions