FrontendUI Components
Theming
Color system, dark mode, and CSS custom properties.
Color System
HyperSaaS uses CSS custom properties with the oklch color space, following shadcn/ui's theming conventions. The primary color is sky blue (hue ~228-232).
Light Theme
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.588 0.155 230);
--primary-foreground: oklch(0.985 0.002 230);
--secondary: oklch(0.97 0.001 286.375);
--accent: oklch(0.97 0.001 286.375);
--muted: oklch(0.97 0.001 286.375);
--destructive: oklch(0.577 0.245 27.325);
--ring: oklch(0.588 0.155 230);
--sidebar-primary: oklch(0.588 0.155 230);
}Dark Theme
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--primary: oklch(0.746 0.138 232);
--primary-foreground: oklch(0.208 0.042 231);
--ring: oklch(0.746 0.138 232);
--sidebar-primary: oklch(0.746 0.138 232);
}Color Tokens
| Token | Purpose |
|---|---|
--background / --foreground | Page background and text |
--primary / --primary-foreground | Primary buttons, links, accents |
--secondary / --secondary-foreground | Secondary elements |
--muted / --muted-foreground | Subtle backgrounds, disabled text |
--accent / --accent-foreground | Hover states, highlights |
--destructive / --destructive-foreground | Error states, delete buttons |
--card / --card-foreground | Card backgrounds |
--popover / --popover-foreground | Dropdown/popover backgrounds |
--border | Border color |
--input | Input border color |
--ring | Focus ring color |
--sidebar-* | Sidebar-specific colors |
--chart-1 through --chart-5 | Chart colors |
Dark Mode
Dark mode is handled by next-themes:
// components/theme-provider.tsx
import { ThemeProvider as NextThemesProvider } from "next-themes";
export function ThemeProvider({ children }) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</NextThemesProvider>
);
}The attribute="class" strategy adds/removes the dark class on <html>, which activates the dark CSS variables.
Theme Toggle
import { useTheme } from "next-themes";
function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<Button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
{theme === "dark" ? <SunIcon /> : <MoonIcon />}
</Button>
);
}Customizing Colors
To change the primary color, update the CSS variables in globals.css. The oklch color space makes it easy to adjust:
- Hue (~228-232): Shift to change the color family
- Chroma (~0.138-0.155): Increase for more saturated, decrease for muted
- Lightness (~0.588 light / ~0.746 dark): Adjust for contrast
Example — switching to violet:
:root {
--primary: oklch(0.588 0.155 280); /* Changed hue from 230 to 280 */
}
.dark {
--primary: oklch(0.746 0.138 282);
}Fonts
The app uses Geist font family:
// app/layout.tsx
import { Geist, Geist_Mono } from "next/font/google";
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });Applied via CSS variables:
body {
font-family: var(--font-geist-sans), sans-serif;
}
code {
font-family: var(--font-geist-mono), monospace;
}Tailwind CSS 4
The project uses Tailwind CSS v4 with the new @plugin directive:
/* globals.css */
@import "tailwindcss";
@plugin "@tailwindcss/typography";Components use Tailwind utility classes that reference the CSS custom properties:
<button className="bg-primary text-primary-foreground hover:bg-primary/90">
Click me
</button>