Theming
Monark ships a single theme item that configures every color, radius, and font used by the rest of the registry. It's the first thing the npx shadcn add flow installs, and it sets defaults you can override at any layer.
The theme is the Monark 2026 brand: a warm cream light mode, an espresso dark mode, flat Monark orange (#f88d10) as the primary with dark text on it, a 1rem radius, and Nunito Sans. To try it with your own brand color before installing, open the theme panel from the sun/moon button in this site's header.
How the theme is delivered
When you ran npx shadcn add @monark/theme, shadcn wrote a @theme inline block into your global CSS, a :root + .dark pair of CSS variables, and an @supports block. Every Monark component reads those variables; nothing is hardcoded.
:root {
--surface-tint: 1;
--primary: #f88d10;
--background: #fff9f3; /* cream */
--primary-foreground: var(--foreground); /* dark text on orange */
--radius: 1rem;
/* ... */
}
.dark {
--background: #1a0b02; /* espresso */
/* ... */
}
@supports (color: oklch(from red l c h)) {
:root {
--background: oklch(from var(--primary) 0.985 calc(c * 0.06 * var(--surface-tint)) h);
/* ... */
}
.dark {
--background: oklch(from var(--primary) 0.17 calc(c * 0.2 * var(--surface-tint)) h);
/* ... */
}
}
Surfaces follow the primary
Inside @supports, every surface, border, and text token is computed from --primary with CSS relative color syntax. Each token keeps its own lightness (so contrast doesn't move), takes the primary's hue, and a small share of its chroma, scaled by --surface-tint.
- Change
--primaryand the surfaces follow. A teal primary gives faintly teal surfaces with the same contrast. --surface-tintsets how much.1is the Monark standard;0gives neutral (untinted) surfaces while keeping the primary; up to2tints them more.--primary-inkis the primary darkened enough to read as text or a link on light backgrounds (orange itself is too light for text on cream). In dark mode it equals--primary.
Relative color syntax needs Chrome 119+, Safari 16.4+, or Firefox 128+. Older browsers skip the @supports block and use the fixed values above it, which are the Monark colors precomputed from the same formulas; there, surfaces don't follow a changed --primary.
Remapping a color
To change the accent from Monark orange to, say, teal, override --primary on :root. The surfaces, borders, and --primary-ink follow in both modes:
:root {
--primary: #14b8a6; /* teal */
--surface-tint: 1; /* 0 for neutral surfaces */
}
The theme panel's "Copy CSS" button gives you exactly this block. Text on the primary stays dark (--primary-foreground is --foreground in light mode, --background in dark); if you pick a dark primary, set --primary-foreground to a light color too.
Every component that uses primary (Button's default variant, links, chart colors) updates without touching a single component file.
Available tokens
Monark exposes the full shadcn token set plus a few of its own:
| Token | Used by |
|---|---|
--background / --foreground | Page body |
--card / --card-foreground | Card, Wallet, Dialog content |
--popover / --popover-foreground | Popover, Dropdown, Select |
--primary / --primary-foreground | Button default, links |
--primary-ink | Orange-family text and links on light surfaces |
--surface-tint | How strongly surfaces follow the primary (0–2) |
--secondary / --secondary-foreground | Button secondary, Badge secondary |
--muted / --muted-foreground | Placeholder text, dimmed UI |
--accent / --accent-foreground | Hover states, menu highlights |
--destructive / --destructive-foreground | Destructive button, error text |
--success / --warning | Status colors, always paired with a label or icon |
--overlay | Dialog and sheet backdrop (black at 45%) |
--border / --input / --ring | All borders, form inputs, focus ring |
--chart-1 ... --chart-5 | Recharts palette |
--sidebar* | Sidebar block backgrounds and borders |
--radius | The radius scale below |
--font-sans / --font-heading / --font-mono | Body text, headings and dialog titles, numbers and code |
--ring is --primary-ink in light mode: flat orange is only 2.3:1 on cream, under the 3:1 minimum for focus indicators. In dark mode it's the primary itself.
Changing the radius
Every rounded-* step is a multiple of --radius, so one variable reshapes the whole UI:
| Utility | Value | At 1rem | Used by |
|---|---|---|---|
rounded-sm | radius × 0.5 | 8px | Small chips inside a control |
rounded-md | radius × 0.75 | 12px | Inputs, textareas, menu items |
rounded-lg | radius | 16px | Menus, popovers, tooltips |
rounded-xl | radius × 1.25 | 20px | Select and dropdown items |
rounded-2xl | radius × 1.5 | 24px | Sub-panels, stat tiles, popover content |
rounded-3xl | radius × 2 | 32px | Cards and dialogs |
rounded-4xl | radius × 2.5 | 40px | Hero media |
rounded-full | pill | — | Buttons, badges, tabs, select triggers |
:root {
--radius: 0.5rem; /* tighter; default is 1rem */
}
Changing the font
Monark uses Nunito Sans. In Next.js, load it with next/font and put its variable first in --font-sans (--font-sans: var(--font-nunito), "Nunito Sans", ui-sans-serif, ...); elsewhere, a Google Fonts @import works. --font-heading follows --font-sans unless you set it. To swap:
- Replace the
@import url(...)at the top of your globals CSS (or usenext/font). - Update
--font-sansto match the new family.
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap");
:root {
--font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
}
Base styles and utilities
The theme also writes a small base layer, the same one every Monark site runs:
- a 1.6 body line height, antialiased text, and
scroll-padding-top: 5.5remso anchor links clear a sticky header; - headings
h1–h4at a 1.15 line height withtext-wrap: balance; - a 2px
--ringoutline on every:focus-visibleelement, offset by 2px; - a pointer cursor on every enabled control (buttons,
select,summary,label[for], checkbox, radio, range, file and color inputs, and thebutton,checkbox,switch,radio,tab,option,menuitem*and non-inputcomboboxroles), andnot-allowedon anything:disabled,aria-disabled="true"ordata-disabled; - a
prefers-reduced-motionreset that turns animations and transitions off.
It lives in @layer base, so any utility class on an element still wins. The cursor rules are wrapped in :where() as well, so cursor-default on a component overrides them.
Five utilities come with it:
| Utility | Does |
|---|---|
eyebrow | Small uppercase label above a heading: 0.75rem, bold, 0.08em tracking |
tracking-display | Tight -0.02em tracking for display headings |
tnum | Tabular figures, so amounts line up in columns |
no-scrollbar | Hides the scrollbar on a scrolling row while keeping it scrollable |
animate-expand | Height animation for a Radix Collapsible or Accordion panel |
<p className="eyebrow text-primary-ink">How it works</p>
<h2 className="text-3xl font-extrabold tracking-display">Three steps</h2>
Expanding panels
animate-expand clips the element (overflow: clip, with a 4px overflow-clip-margin so a child's focus ring at the edge stays visible) and animates its height over 0.2s ease-out: open on data-state="open", closed on data-state="closed". The keyframes come from tw-animate-css and read --radix-collapsible-content-height, which Radix sets on both Collapsible and Accordion content. Under prefers-reduced-motion the panel opens and closes instantly. Accordion, Collapsible and Disclosure already use it; reach for it when you build your own panel:
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
<CollapsiblePrimitive.Root>
<CollapsiblePrimitive.Trigger>Fee breakdown</CollapsiblePrimitive.Trigger>
{/* Animate the element Radix gives data-state; pad an inner div */}
<CollapsiblePrimitive.Content className="animate-expand">
<div className="px-4 py-3 text-sm">Network fee, protocol fee, total.</div>
</CollapsiblePrimitive.Content>
</CollapsiblePrimitive.Root>
Put padding, margins and borders on an inner element, not on the animated one: the animation runs the height from 0, and padding on the same element would show before it starts and snap away when it ends. Closing animates too, because Radix keeps the content mounted until the exit animation finishes. Safari doesn't support overflow-clip-margin yet and clips at the edge, so leave a little inner padding there if a child's focus ring sits right at the edge.
Dark mode
Toggle dark mode with the .dark class on <html>. Monark's components don't care how you toggle it; pair with next-themes or roll your own. Every token has a dark-mode counterpart.
Chart palette
The five --chart-* tokens drive the Chart block's default color ramp. --chart-1 follows the primary, capped at oklch lightness 0.62 in light mode (#cd6600 for the default orange) because flat orange is only 2.3:1 on cream. The other four are fixed warm tones (red, brown, khaki tan, and a neutral) so they stay distinguishable from it. Every token meets the 3:1 contrast that chart marks need against the background and cards. Remap if you want a cooler palette, and check the light-mode values keep that 3:1:
:root {
--chart-1: oklch(0.55 0.15 240); /* blue */
--chart-2: oklch(0.5 0.18 290); /* purple */
--chart-3: oklch(0.55 0.12 165); /* teal */
--chart-4: oklch(0.6 0.12 85); /* ochre */
--chart-5: oklch(0.55 0.2 25); /* red */
}
The previous theme
Versions before 1.0.0 shipped a zinc and orange theme with a 0.5rem radius. It stays installable from its frozen release:
npx shadcn@latest add https://ui.monark.io/r/v0.1.0/theme.json
When to fork vs override
Override via CSS variables when you want to re-skin. Fork (edit components/ui/*.tsx) when you need structural changes: different Radix primitives, additional slots, different accessibility semantics. Monark's components sit in your repo after install, so either path is yours to take.