Skip to content

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 --primary and the surfaces follow. A teal primary gives faintly teal surfaces with the same contrast.
  • --surface-tint sets how much. 1 is the Monark standard; 0 gives neutral (untinted) surfaces while keeping the primary; up to 2 tints them more.
  • --primary-ink is 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:

TokenUsed by
--background / --foregroundPage body
--card / --card-foregroundCard, Wallet, Dialog content
--popover / --popover-foregroundPopover, Dropdown, Select
--primary / --primary-foregroundButton default, links
--primary-inkOrange-family text and links on light surfaces
--surface-tintHow strongly surfaces follow the primary (0–2)
--secondary / --secondary-foregroundButton secondary, Badge secondary
--muted / --muted-foregroundPlaceholder text, dimmed UI
--accent / --accent-foregroundHover states, menu highlights
--destructive / --destructive-foregroundDestructive button, error text
--success / --warningStatus colors, always paired with a label or icon
--overlayDialog and sheet backdrop (black at 45%)
--border / --input / --ringAll borders, form inputs, focus ring
--chart-1 ... --chart-5Recharts palette
--sidebar*Sidebar block backgrounds and borders
--radiusThe radius scale below
--font-sans / --font-heading / --font-monoBody 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:

UtilityValueAt 1remUsed by
rounded-smradius × 0.58pxSmall chips inside a control
rounded-mdradius × 0.7512pxInputs, textareas, menu items
rounded-lgradius16pxMenus, popovers, tooltips
rounded-xlradius × 1.2520pxSelect and dropdown items
rounded-2xlradius × 1.524pxSub-panels, stat tiles, popover content
rounded-3xlradius × 232pxCards and dialogs
rounded-4xlradius × 2.540pxHero media
rounded-fullpill—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:

  1. Replace the @import url(...) at the top of your globals CSS (or use next/font).
  2. Update --font-sans to 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.5rem so anchor links clear a sticky header;
  • headings h1–h4 at a 1.15 line height with text-wrap: balance;
  • a 2px --ring outline on every :focus-visible element, offset by 2px;
  • a pointer cursor on every enabled control (buttons, select, summary, label[for], checkbox, radio, range, file and color inputs, and the button, checkbox, switch, radio, tab, option, menuitem* and non-input combobox roles), and not-allowed on anything :disabled, aria-disabled="true" or data-disabled;
  • a prefers-reduced-motion reset 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:

UtilityDoes
eyebrowSmall uppercase label above a heading: 0.75rem, bold, 0.08em tracking
tracking-displayTight -0.02em tracking for display headings
tnumTabular figures, so amounts line up in columns
no-scrollbarHides the scrollbar on a scrolling row while keeping it scrollable
animate-expandHeight 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.