Skip to Content
Found it useful? Support us with a 5 ⭐ review on ThemeForest!
Styling & Theming

Styling & Theming

This template uses Tailwind CSS 4.1, shadcn/ui (Radix-powered), CVA for variants, and tailwind-merge for safe class merging. Design tokens are exposed via CSS variables and mapped to Tailwind using the @theme inline block.


Theme Provider

Light/Dark mode is driven by src/providers/theme-provider.tsx and a simple hook:

// Toggle theme example import { useTheme } from "@/hooks/use-theme" const { theme, setTheme } = useTheme() setTheme(theme === "dark" ? "light" : "dark")
  • The provider adds/removes the .dark class on <html> and syncs with system preference.
  • Components read colors from semantic tokens (e.g. bg-card, text-foreground).

Token System (CSS Variables → Tailwind)

Tokens are defined in index.css using :root (light) and .dark (dark). Then they are mapped to Tailwind tokens in @theme inline.

  • Define raw variables:
:root { --background: #fff; --foreground: #0a0a0a; --primary: #1c67f3; /* ... */ } .dark { --background: #121316; --foreground: #fafafa; --primary: #1c67f3; /* ... */ }
  • Expose to Tailwind:
@theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --color-primary: var(--primary); /* ... more mappings ... */ }

Result: you can use semantic utilities like bg-background, text-foreground, bg-card, border-border across the app. Switching theme simply flips the underlying CSS variables.


Dark Variant (Tailwind 4)

We enable a custom dark variant to target inner scopes:

@custom-variant dark (&:is(.dark *));

Usage examples:

<div className="bg-card dark:bg-card">...</div> <p className="text-foreground/70 dark:text-foreground/80">...</p>

shadcn/ui

All primitives in src/components/ui are generated/structured like shadcn components:

  • Accessible by default (keyboard/focus, ARIA).
  • Theme-aware via Tailwind tokens.
  • Extendable with className and CVA variants.

Add new primitives:

npx shadcn@latest add button # or any other component name

Component Variants with CVA

Use CVA to declare design-system variants and sizes in a single source of truth.

// src/components/ui/button.tsx (excerpt) import { cva, type VariantProps } from "class-variance-authority" import { twMerge } from "tailwind-merge" export const buttonStyles = cva( "inline-flex items-center justify-center rounded-2xl font-medium transition-colors disabled:opacity-50 disabled:pointer-events-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:opacity-95", ghost: "bg-transparent text-foreground hover:bg-card", outline: "border border-border bg-background hover:bg-card", link: "underline-offset-4 hover:underline text-primary", }, size: { sm: "h-9 px-3 text-sm", md: "h-10 px-4 text-sm", lg: "h-11 px-5 text-base", }, tone: { success: "bg-success text-success-foreground", warning: "bg-warning text-warning-foreground", destructive: "bg-destructive text-destructive-foreground", }, }, compoundVariants: [ { variant: "outline", tone: "destructive", class: "border-destructive-border" }, ], defaultVariants: { variant: "default", size: "md" }, } ) export type ButtonVariants = VariantProps<typeof buttonStyles> export function cn(...classes: (string | undefined)[]) { return twMerge(classes.filter(Boolean).join(" ")) }

Use it in a component:

import { buttonStyles } from "@/components/ui/button" <button className={buttonStyles({ variant: "outline", size: "lg" })}> Continue </button>

Why CVA + tailwind-merge?

  • CVA gives typed variants (great DX).
  • tailwind-merge prevents conflicting utilities (e.g. px-2 vs px-4).

Project Fonts & Base Styles

Fonts are imported in index.css:

@import url('https://fonts.googleapis.com/css2?family=Inria+Serif:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap'); @import "tailwindcss"; @import "tw-animate-css"; .font-second { font-family: var(--second-font-family); }

Base resets and typography scale live in @layer base:

@layer base { * { @apply border-border outline-ring/50; } html { @apply h-full; scroll-behavior: smooth; } body { @apply bg-layout text-foreground min-h-full; } h1 { @apply text-xl md:text-2xl lg:text-3xl; } p { @apply text-sm md:text-base text-foreground; } }

Utility Add-ons

Custom utilities (e.g., extra font sizes) can be defined in @layer utilities:

@layer utilities { .text-2xs { font-size: 11px; } }

Example: Themed Card

export function Card({ children }: { children: React.ReactNode }) { return ( <div className="rounded-xl bg-card text-card-foreground shadow-[0_1px_2px_0_var(--color-shadow-2),0_2px_12px_-2px_var(--color-shadow-12)]"> {children} </div> ) }
  • Uses bg-card & text-card-foreground (mapped to CSS vars).
  • Shadow tokens come from --color-shadow-* to keep elevation consistent.

Tips & Conventions

  • Keep /src/components/ui minimal (shadcn primitives). Create feature UI in /components/custom.
  • Add new tokens in :root + .dark, then map under @theme inline.
  • Prefer semantic utilities (bg-card, text-foreground) over raw colors.
  • Variants first: use CVA to encode states (size, tone, emphasis) instead of scattering classes.
  • Don’t edit generated shadcn code heavily — extend via wrappers or className to simplify updates.
Last updated on