Overview
The theme is driven by Tailwind classes that map to CSS variables. Color tokens live in constants/colors.ts
. Tailwind exposes them as --color-*
variables and generates utility classes you use across the UI.
Token source
// constants/colors.ts
export const BaseColors = {
highlight: '246 174 45',
accent: '92 148 110',
neutral: '200 183 166',
'primary-dark': '25 19 14',
'secondary-dark': '35 29 25',
'primary-light': '252 243 227',
'secondary-light': '245 228 211',
success: '58 157 122',
destructive: '222 82 69',
} as const;
Tokens are RGB lists. Tailwind turns them into rgb(var(--color-…)/alpha)
. Use classes like bg-secondary-light
or text-primary-dark/90
.
How Tailwind maps tokens
// tailwind.config.ts — tokens → utilities + CSS vars
import { BaseColors } from './constants/colors';
export default {
theme: {
extend: {
colors: Object.fromEntries(
Object.entries(BaseColors).map(([k, v]) => [
k,
`rgb(var(--color-${k}, ${v}) / <alpha-value>)`,
]),
),
},
},
plugins: [
function ({ addBase }) {
addBase({
':root': Object.fromEntries(
Object.entries(BaseColors).map(([k, v]) => [`--color-${k}`, v]),
),
});
},
],
};
Use in components
export function Card() {
return (
<div className="rounded-xl bg-secondary-light p-4 text-primary-dark/90 dark:bg-secondary-dark dark:text-primary-light">
Themed card
</div>
);
}
Dark mode
<!-- Toggle the class on <html> -->
<html class="dark">
<!-- … -->
</html>
Use dark:
variants on classes. The dark
class on <html>
switches the palette for the whole app.
Preview theme
// tailwind.config.ts plugin (excerpt)
addBase({
'[data-theme="preview"]': Object.fromEntries(
Object.entries(BaseColors).map(([key]) => [
`--color-${key}`,
`var(--preview-color-${key})`,
]),
),
});
Wrap a preview with temporary colors:
<div
data-theme="preview"
style={
{
// override a few tokens on the fly
['--preview-color-highlight' as any]: '255 99 71',
['--preview-color-accent' as any]: '56 189 248',
} as React.CSSProperties
}
className="rounded-xl bg-secondary-light p-6"
>
Live preview
</div>
Preview mode lets you try palettes without changing the saved theme.
Screens
Define breakpoints once as tokens so you can reuse them across the app.
// constants/screens.ts
export const Screen = {
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
'2xl': 1536
} as const;
Plug the tokens into Tailwind screens
so sm:
, md:
, etc. use your values.
// tailwind.config.ts
screens: {
sm: `${Screen.sm}px`,
md: `${Screen.md}px`,
lg: `${Screen.lg}px`,
xl: `${Screen.xl}px`,
'2xl': `${Screen['2xl']}px`,
}
Animations
Define keyframes and animation utilities in Tailwind.
// tailwind.config.ts (keyframes + utilities)
extend: {
keyframes: {
'accordion-down': { from: { height: '0' }, to: { height: 'var(--radix-accordion-content-height)' } },
'accordion-up': { from: { height: 'var(--radix-accordion-content-height)' }, to: { height: '0' } },
'vote-shake': { '0%,100%': { transform: 'translateY(0)' }, '25%': { transform: 'translateY(-2px)' }, '75%': { transform: 'translateY(2px)' } },
'scale-pop': { '0%': { transform: 'scale(1)' }, '30%': { transform: 'scale(1.2)' }, '60%': { transform: 'scale(0.95)' }, '100%': { transform: 'scale(1)' } },
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'vote-shake': 'vote-shake 0.25s ease-in-out',
'scale-pop': 'scale-pop 400ms ease-out',
},
}
Use the animation in components with animate-*
classes.
<button className="animate-scale-pop">Click</button>
The project includes tailwindcss-animate. Add keyframes in Tailwind and apply them with animate-*
classes.
Global styles
Enable smooth scrolling across the app.
/* app/global.css */
html { scroll-behavior: smooth; }
Customize text selection colors using tokens.
/* app/global.css */
::selection {
background-color: rgb(var(--color-highlight));
color: rgb(var(--color-primary-light));
}
Add a new token
- Add the token to
BaseColors
inconstants/colors.ts
as an RGB string. - Use it via classes like
bg-your-token
,text-your-token/80
. - If Tailwind config was changed, restart the dev server to reload.
// constants/colors.ts
export const BaseColors = {
// existing tokens...
brand: '12 99 210',
} as const;
Use the token in components with Tailwind classes.
<span className="text-brand/90">Brand text</span>