Theme
DaFink UI is fully themeable via CSS custom properties. A color palette, a surface style, and light/dark mode compose freely and independently. Swap any one of them without touching a single component.
Light & dark mode
Mode is class-based: toggling adds or removes .dark on <html>, which flips every theme and style to its dark token map. The choice is stored in localStorage and mirrored to a cookie so the server renders the correct mode on first paint, with no flash.
Installation
npx dafink-ui add theme-toggleColor palettes
Six color palettes ship with DaFink UI as built-in themes, imported from @/src/themes. Each is a color swap only. Radius, shadow, and border come from the active Style, not the Theme. Hover or focus a swatch for its hex value.
defaultPure white surfaces with a jet-black brand, a neutral premium look that works in any context.
oceanDeep sky blue brand over cool blue-tinted surfaces. Dark mode drops into a true deep navy with precise elevation steps.
emberWarm orange brand with amber-tinted surfaces. Dark mode goes to a near-black with deep warm undertones, rich without being aggressive.
forestFresh emerald brand over subtly green-tinted surfaces. Dark mode is a true deep forest: near-black with green undertones and clear hierarchy.
noirCool slate surfaces with a deep charcoal brand. Quieter than Zinc: every surface has a blue undertone that reads refined and editorial.
plumRich violet brand over barely-tinted surfaces. Dark mode is deep violet-black, luxurious, design-forward, and high contrast.
The styles
Three surface styles ship with DaFink UI. Each composes with any theme.
Minimal
Restrained radius, hairline borders, soft shadow. The clean default.
Restrained radius, hairline borders, little to no shadow, generous whitespace, neutral surfaces. The closest fit to a clean, unopinionated default.
Strong. High contrast and clear separation come naturally, with few effects to compromise legibility.
Content-heavy apps, enterprise tools, reading experiences: anything that prioritizes clarity and longevity.
Neumorph
Extruded elements emerge from the surface: depth from dual dark/light shadows, no border.
The surface is locked to a neutral gray so dual shadows (white top-left, dark bottom-right) are visible. Depth comes entirely from shadow, with no border and a medium-to-large radius. Form fields use an inset version of the shadow to feel sunken.
The weakest style for accessibility. Borders vanish and contrast between elements is low. Treat it as opt-in and use it sparingly (toggles, sliders, and cards only) while keeping the rest of the UI flat.
Sparingly, for tactile accents on individual controls. Do not build a whole interface in Neumorph.
Brutalist
Raw and bold: square corners, thick borders, hard offset shadow, high contrast.
Radius 0, thick solid borders (2–4px), and a hard offset shadow (e.g. 4px 4px 0 with no blur). High contrast, with bold or monospaced type.
Generally strong. High contrast and visible, heavy borders make boundaries and focus easy to perceive.
Brands that want to stand out: portfolios, editorial sites, developer tools, and marketing pages.
Applying a theme
Every component in DaFink UI is styled through token classes like bg-brand and text-text-muted, which resolve to CSS custom properties defined in globals.css. Pass a theme's light or dark token map as an inline style on any wrapper element, and every DaFink UI component beneath it re-themes automatically.
// Wrap your preview area with the theme's token map
import { oceanTheme } from '@/src/themes/ocean';
<div style={oceanTheme.light as React.CSSProperties}>
<YourApp />
</div>This docs site uses this pattern in the top nav's theme and style dropdowns: selecting a value injects the token map onto a style override that wraps the whole preview area.
Creating a custom theme
A theme is a plain TypeScript object that satisfies the Theme interface from @/src/themes/types. You only need to override the tokens you want to change. Start from a copy of defaultTheme and adjust the brand and surface palette.
import type { Theme } from '@/src/themes/types';
export const myTheme: Theme = {
name: 'my-theme',
label: 'My Theme',
accent: '#0ea5e9', // swatch hex shown in the picker
light: {
'--color-brand': '#0284c7',
'--color-brand-hover': '#0369a1',
'--color-brand-active': '#075985',
'--color-brand-ring': '#0284c7',
'--color-brand-fg': '#ffffff',
// surface, text, input tokens ...
},
dark: {
'--color-brand': '#38bdf8',
'--color-brand-hover': '#7dd3fc',
'--color-brand-active': '#bae6fd',
'--color-brand-ring': '#38bdf8',
'--color-brand-fg': '#0c4a6e',
// surface, text, input tokens ...
},
};Define both light and dark token maps. Do not rely on cascade fallbacks: if a token is not in the map it will fall back to the global default, which may not match your intended palette.
CSS variable approach
The global token defaults live in app/globals.css under the @theme block. These define the baseline palette used by all components when no theme wrapper is present. Dark mode overrides sit immediately below in the .dark selector.
These values are generated, not hand-written. The source of truth is the set of JSON files in tokens/ (one per theme, per mode, e.g. tokens/zinc.light.json), plus tokens/motion.json. Running npm run tokens compiles those files into src/tokens/colors.ts and the CSS custom properties in globals.css. Edit the JSON, then re-run the script. Do not edit the generated files directly.
Themes do not need to redefine every token, only the ones that differ from the defaults. The recommended starting point is to override the five brand tokens (--color-brand, --color-brand-hover, --color-brand-active, --color-brand-ring, --color-brand-fg) and adjust the surface palette if your brand color is strongly chromatic.