6 KiB
| name | description |
|---|---|
| theming | Customize the app's visual design — install Google Fonts via @fontsource, change the color scheme, configure light/dark themes, and apply consistent component styling patterns with Tailwind and CSS variables. |
Theming, Fonts, and Color Schemes
Use this skill when the user wants to change fonts, colors, light/dark appearance, or general visual styling. The template ships with a complete light/dark theme system built on CSS custom properties and Tailwind, plus a useTheme hook for runtime switching.
Adding Fonts
Any Google Font can be installed via the @fontsource / @fontsource-variable packages.
-
Install the font package. Prefer the variable version when available.
npm install @fontsource-variable/interPackage naming:
@fontsource-variable/<font-name>— variable fonts (preferred; one file, all weights)@fontsource/<font-name>— static fonts
-
Import the font once in
src/main.tsx:import '@fontsource-variable/inter'; -
Register the family in the
@themeblock ofsrc/index.css(Tailwind v4 is CSS-first — there is notailwind.config.ts):@theme { --font-sans: 'Inter Variable', 'Inter', system-ui, sans-serif; }This makes
font-sans(and the default body font) resolve to the new family.
Suggested families by use case
- Modern / Clean: Inter Variable, Outfit Variable, Manrope
- Professional / Corporate: Roboto, Open Sans, Source Sans Pro
- Creative / Artistic: Poppins, Nunito, Comfortaa
- Monospace / Code: JetBrains Mono, Fira Code, Source Code Pro
For expressive hierarchies, pair a sans body font with a display/serif heading font (e.g. Inter + Playfair Display) and expose the second family as another @theme token (e.g. --font-serif or --font-display), which Tailwind turns into a font-serif / font-display utility.
Color Schemes
Colors are defined as CSS custom properties in src/index.css under two selectors:
:root— light-mode values.dark— dark-mode overrides
When the user requests a new color scheme:
- Update both
:rootand.darkinsrc/index.css. Each variable is a full color value wrapped inhsl(...), e.g.--primary: hsl(222.2 47.4% 11.2%);. The@theme inlineblock above maps each--<token>to its--color-<token>utility, so you don't edit it when only changing values. - Keep contrast ratios ≥ 4.5:1 for body text and interactive elements. Test both modes.
- Add new color tokens under the
@theme inlineblock insrc/index.css(Tailwind v4 is CSS-first — there is notailwind.config.ts). Define the raw value as a--<token>on:root/.dark, then expose it as--color-<token>: var(--<token>);inside@theme inline. - Apply colors through semantic tokens (
bg-primary,text-muted-foreground,border-input) rather than raw palette names when possible, so future theme changes propagate.
The shadcn/ui components already consume these semantic tokens, so changing the variables automatically restyles the entire component library.
Light/Dark Theme Switching
The template includes:
useThemehook (src/hooks/useTheme.ts) — read and set the current theme programmatically.- CSS custom properties in
src/index.css— one set in:root, dark overrides in.dark. - Automatic persistence via the
AppContextconfig (config.theme), which is saved to local storage.
To add a theme toggle:
import { useTheme } from '@/hooks/useTheme';
import { Button } from '@/components/ui/button';
import { Moon, Sun } from 'lucide-react';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
{theme === 'dark' ? <Sun className="size-4" /> : <Moon className="size-4" />}
</Button>
);
}
Component Styling Patterns
- Class merging: use the
cn()utility (@/lib/utils) to combine conditional classes and override defaults without class-order bugs. - Variants: follow shadcn/ui's
class-variance-authoritypattern for component variants (variant,size). Copy an existing ui/ component as a template. - Responsive design: lean on Tailwind breakpoints (
sm:,md:,lg:) rather than JS media queries. UseuseIsMobileonly when layout must change based on JS-measured viewport. - Interactive states: always define
hover:,focus-visible:, anddisabled:states for clickable elements. Focus rings should usering-ring/ring-offset-backgroundso they pick up theme colors. - Spacing: an 8px grid (Tailwind's default 4-based scale) keeps visual rhythm consistent. Common paddings:
p-4,p-6; gaps:gap-2,gap-4. - Depth: soft shadows (
shadow-sm,shadow-md), subtle gradients, androunded-lg/rounded-xlcorners match the template's aesthetic. Avoid heavy drop shadows.
Negative z-index gotcha
When placing decorative elements behind content with -z-10 (e.g. blurred background gradients), add isolate to the parent container. Without isolate, the negative z-index escapes the local stacking context and the element disappears behind the page's background color.
<section className="relative isolate">
<div className="absolute inset-0 -z-10 bg-linear-to-br from-primary/20 to-transparent" />
{/* content */}
</section>
Design Quality Checklist
Before finishing a visual change, verify:
- Both light and dark modes look correct — no hard-coded colors, all text readable.
- Contrast ratios meet WCAG AA (≥ 4.5:1 for body, ≥ 3:1 for large text).
- Interactive elements have visible
hover,focus-visible, anddisabledstates. - Layout is responsive down to ~360px width without horizontal scroll.
- Animations respect
prefers-reduced-motion(Tailwind:motion-safe:/motion-reduce:). - Spacing is consistent — no one-off
p-[13px]style values.