Customization
Dark Mode
How dark mode works in trink-ui, per-section theming, system preference detection, and implementing a theme toggle.
How Dark Mode Works
trink-ui uses the .dark class strategy. When the .dark class is present on an element (typically <html> or a section wrapper), the dark color variables take effect for that element and all its descendants.
/* Light mode — default */
:root {
--trinkui-bg: 255 255 255;
--trinkui-fg: 10 10 10;
--trinkui-primary: 99 102 241;
}
/* Dark mode — activated by .dark class */
.dark {
--trinkui-bg: 9 9 11;
--trinkui-fg: 250 250 250;
--trinkui-primary: 129 140 248;
}
/* To enable dark mode globally, add .dark to <html>: */
<html class="dark">
<!-- All trink-ui components will now use dark colors -->
</html>Per-Section Theming
Every section component accepts a theme prop. This lets you mix light and dark sections on the same page without changing the global theme. The Section component internally applies the .dark class to its own wrapper, scoping the dark variables to just that section.
import { HeroCentered, FeaturesGrid, CTACentered } from "@trinkui/react";
export default function LandingPage() {
return (
<>
{/* Dark hero section */}
<HeroCentered
theme="dark"
title="Ship faster"
subtitle="Production-ready landing page components."
primaryAction={{ label: "Get Started", href: "/signup" }}
/>
{/* Light features section */}
<FeaturesGrid
theme="light"
title="Why trink-ui?"
features={features}
/>
{/* Dark CTA at the bottom */}
<CTACentered
theme="dark"
title="Ready to start?"
subtitle="Join thousands of developers."
primaryAction={{ label: "Sign Up", href: "/signup" }}
/>
</>
);
}Auto-Detection with prefers-color-scheme
Detect the user's system preference and apply dark mode automatically. Place this script in your <head> to prevent a flash of unstyled content (FOUC):
<head>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
var theme = localStorage.getItem('theme');
if (theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
})();
`,
}}
/>
</head>This script runs synchronously before the page renders, so users never see a flash of the wrong theme.
Implementing a Theme Toggle
A simple React hook and toggle button for switching between light and dark mode:
"use client";
import { useState, useEffect } from "react";
export function useTheme() {
const [theme, setTheme] = useState<"light" | "dark">("light");
useEffect(() => {
// Read initial theme from <html> class
const isDark = document.documentElement.classList.contains("dark");
setTheme(isDark ? "dark" : "light");
}, []);
function toggleTheme() {
const next = theme === "light" ? "dark" : "light";
setTheme(next);
if (next === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
localStorage.setItem("theme", next);
}
return { theme, toggleTheme };
}"use client";
import { useTheme } from "@/hooks/useTheme";
export function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
aria-label={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
className="rounded-lg p-2 text-[rgb(var(--trinkui-muted))] hover:bg-[rgb(var(--trinkui-surface))] hover:text-[rgb(var(--trinkui-fg))]"
>
{theme === "light" ? (
{/* Moon icon */}
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
) : (
{/* Sun icon */}
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="5" />
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
</svg>
)}
</button>
);
}