Theming & Design Tokens
Nostromo UI's theming system is built around CSS variables in HSL format that integrate directly with Tailwind CSS. This provides maximum flexibility and performance without runtime overhead.
🎨 Quick Start
1. Import Base CSS
// In your entry file (e.g. main.tsx)
import "@nostromo/ui-tw/styles/base.css";
import "@nostromo/ui-tw/themes/nostromo.css"; // choose or customize theme2. Apply Theme
<html data-theme="nostromo" data-color-scheme="light">
<!-- Your content -->
</html>3. Customize Brand Colors
[data-theme="mybrand"] {
/* Only change what you need */
--color-brand-500: 220 100% 50%; /* Your brand blue */
--color-brand-600: 220 100% 40%; /* Darker variant */
--color-brand-700: 220 100% 30%; /* Even darker */
}🎯 Design Tokens
Color System
All colors use HSL format for easy manipulation:
[data-theme="nostromo"] {
/* Brand colors */
--color-brand-50: 262 84% 95%;
--color-brand-100: 262 84% 90%;
--color-brand-200: 262 84% 80%;
--color-brand-300: 262 84% 70%;
--color-brand-400: 262 84% 60%;
--color-brand-500: 262 84% 52%; /* Primary brand */
--color-brand-600: 262 84% 45%;
--color-brand-700: 262 84% 35%;
--color-brand-800: 262 84% 25%;
--color-brand-900: 262 84% 15%;
--color-brand-950: 262 84% 8%;
/* Neutral colors */
--color-neutral-50: 0 0% 98%;
--color-neutral-100: 0 0% 96%;
--color-neutral-200: 0 0% 90%;
--color-neutral-300: 0 0% 83%;
--color-neutral-400: 0 0% 64%;
--color-neutral-500: 0 0% 45%;
--color-neutral-600: 0 0% 32%;
--color-neutral-700: 0 0% 25%;
--color-neutral-800: 0 0% 15%;
--color-neutral-900: 0 0% 9%;
--color-neutral-950: 0 0% 4%;
/* Semantic colors */
--color-success-500: 142 76% 36%;
--color-warning-500: 38 92% 50%;
--color-error-500: 0 84% 60%;
--color-info-500: 199 89% 48%;
}Spacing & Sizing
[data-theme="nostromo"] {
/* Spacing scale */
--spacing-xs: 0.25rem; /* 4px */
--spacing-sm: 0.5rem; /* 8px */
--spacing-md: 1rem; /* 16px */
--spacing-lg: 1.5rem; /* 24px */
--spacing-xl: 2rem; /* 32px */
--spacing-2xl: 3rem; /* 48px */
--spacing-3xl: 4rem; /* 64px */
/* Border radius */
--radius-none: 0px;
--radius-sm: 0.25rem; /* 4px */
--radius-md: 0.5rem; /* 8px */
--radius-lg: 0.75rem; /* 12px */
--radius-xl: 1rem; /* 16px */
--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);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
}Typography
[data-theme="nostromo"] {
/* Font families */
--font-heading: "Inter", system-ui, sans-serif;
--font-body: "Inter", system-ui, sans-serif;
--font-mono: "JetBrains Mono", "Fira Code", monospace;
/* Font sizes */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
--text-5xl: 3rem; /* 48px */
/* Line heights */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
/* Font weights */
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
}🌙 Dark Mode
System-based Dark Mode
@media (prefers-color-scheme: dark) {
[data-theme="nostromo"] {
--color-neutral-50: 0 0% 9%;
--color-neutral-900: 0 0% 98%;
}
}Manual Dark Mode Toggle
[data-theme="nostromo"][data-color-scheme="dark"] {
--color-neutral-50: 0 0% 9%;
--color-neutral-900: 0 0% 98%;
--color-brand-500: 262 84% 60%; /* Lighter brand for contrast */
}// React hook for dark mode
function useDarkMode() {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
const root = document.documentElement;
root.setAttribute('data-color-scheme', isDark ? 'dark' : 'light');
}, [isDark]);
return [isDark, setIsDark];
}🎨 Predefined Themes
Nostromo (Default)
[data-theme="nostromo"] {
--color-brand-500: 262 84% 52%; /* Purple */
--color-neutral-900: 0 0% 9%; /* Dark background */
--radius-md: 0.5rem;
--font-heading: "Inter", sans-serif;
}Mother
[data-theme="mother"] {
--color-brand-500: 200 100% 50%; /* Cyan */
--color-neutral-900: 220 13% 9%; /* Dark blue-gray */
--radius-sm: 0.25rem;
--font-heading: "Inter", sans-serif;
}LV-426
[data-theme="lv-426"] {
--color-brand-500: 25 95% 53%; /* Orange */
--color-neutral-900: 0 0% 8%; /* Very dark */
--radius-lg: 0.75rem;
--font-heading: "Inter", sans-serif;
}Sulaco
[data-theme="sulaco"] {
--color-brand-500: 210 40% 50%; /* Blue */
--color-neutral-900: 0 0% 10%; /* Dark */
--radius-md: 0.5rem;
--font-heading: "Inter", sans-serif;
}🛠️ Custom Theming
Create Your Own Theme
[data-theme="mybrand"] {
/* Brand colors - only change what you need */
--color-brand-500: 220 100% 50%; /* Your brand blue */
--color-brand-600: 220 100% 40%; /* Darker variant */
--color-brand-700: 220 100% 30%; /* Even darker */
/* Typography */
--font-heading: "Poppins", sans-serif;
--font-body: "Inter", sans-serif;
/* Styling */
--radius-md: 0.75rem;
}Apply Theme
<html data-theme="mybrand">
<!-- Your content -->
</html>Dynamic Theme Switching
// React example
function ThemeToggle() {
const [theme, setTheme] = useState('nostromo');
const toggleTheme = () => {
const newTheme = theme === 'nostromo' ? 'mother' : 'nostromo';
setTheme(newTheme);
document.documentElement.setAttribute('data-theme', newTheme);
};
return (
<button onClick={toggleTheme}>
Switch to {theme === 'nostromo' ? 'Mother' : 'Nostromo'}
</button>
);
}♿ Accessibility
Contrast Guidelines
All colors are designed to meet WCAG 2.1 AA standards:
[data-theme="nostromo"] {
/* Brand colors - validated contrast */
--color-brand-500: 262 84% 52%; /* 4.5:1 contrast on white */
--color-brand-600: 262 84% 45%; /* 7:1 contrast on white */
/* Neutral colors - safe readability */
--color-neutral-900: 0 0% 9%; /* 21:1 contrast on white */
--color-neutral-700: 0 0% 25%; /* 12:1 contrast on white */
}Focus States
/* Automatic focus states */
.focus-visible {
outline: 2px solid hsl(var(--color-brand-500));
outline-offset: 2px;
}🚀 Performance
Bundle Size Optimization
// ✅ Recommended: Per-component imports (smallest bundle)
import { Button } from '@nostromo/ui-core/button';
import { Input } from '@nostromo/ui-core/input';
// ✅ Also OK: Barrel imports
import { Button, Input } from '@nostromo/ui-core';
// ❌ Avoid: Full library import
import * as Nostromo from '@nostromo/ui-core';Tailwind Configuration
// tailwind.config.js
const nostromoPreset = require("@nostromo/ui-tw/tailwind.preset.js");
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@nostromo/**/*.{js,ts,jsx,tsx}"
],
presets: [nostromoPreset],
// Purge unused CSS
purge: {
enabled: true,
content: ['./src/**/*.{js,ts,jsx,tsx}'],
}
};📚 Form Integration
React Hook Form + Zod
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Input, HelperText, ErrorMessage } from '@nostromo/ui-core';
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema)
});
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<Input
{...register('email')}
placeholder="Email"
className={errors.email ? 'border-error-500' : ''}
/>
{errors.email && (
<ErrorMessage>{errors.email.message}</ErrorMessage>
)}
</div>
<div>
<Input
{...register('password')}
type="password"
placeholder="Password"
className={errors.password ? 'border-error-500' : ''}
/>
{errors.password && (
<ErrorMessage>{errors.password.message}</ErrorMessage>
)}
<HelperText>Password must be at least 8 characters</HelperText>
</div>
</form>
);
}🎯 Live Examples
Theme Playground
// Live theme switcher - try different themes
function ThemePlayground() {
const themes = ['nostromo', 'mother', 'lv-426', 'sulaco'];
const [currentTheme, setCurrentTheme] = useState('nostromo');
return (
<div className="space-y-4">
<select
value={currentTheme}
onChange={(e) => setCurrentTheme(e.target.value)}
className="px-3 py-2 border rounded-md"
>
{themes.map(theme => (
<option key={theme} value={theme}>{theme}</option>
))}
</select>
<div data-theme={currentTheme} className="p-4 border rounded-lg">
<Button>Test Button</Button>
<Input placeholder="Test Input" />
</div>
</div>
);
}This theming system gives you maximum flexibility to create consistent, performant, and beautiful user interfaces.