40+ landing page components for ReactBrowse now

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.

How the .dark class works
/* 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.

Mixed light/dark sections
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):

layout.tsx — Inline script in <head>
<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:

hooks/useTheme.ts
"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 };
}
ThemeToggle component
"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>
  );
}

Next step

Learn how to override component styles with className and cn().

Override Styles