Skip to content
VVVER

Design System / 02

Colors

Ink and paper reverse with the theme. Neutral surfaces, opacity steps, and optional status tints extend that pair without adding literal colors to components.

Core pair

Ink and paper are semantic roles, not literal light and dark colors. Both reverse in the dark theme, so component code can inherit them without knowing which theme is active.

  • Ink

    --c-black

    Light #040404 · dark #fdfcfb

    Readable text, currentColor drawings, and structural rules at reduced opacity.

  • Paper

    --c-white

    Light #fdfcfb · dark #0b0a09

    The page ground and the opposing color inside an ink section.

Supporting surfaces

Surface tokens are exceptions for content that needs a stable ground. They do not replace hairline structure, and they never turn hover into a brighter or more colorful state.

  • Secondary

    --secondary

    Light #f4f2ef · dark #171614

    Static fills for code, skeletons, and fallback media.

  • Accent surface

    --accent

    Light #ece9e4 · dark #211f1c

    A stronger optional neutral surface, never a hover highlight.

  • Muted foreground

    --muted-foreground

    Light #5d5d5d · dark #a3a19c

    Opaque secondary prose; AA against both shipped paper values.

Host contract

The package is source-shipped. A host defines the light palette and maps ink and paper into Tailwind; tokens.css supplies the matching dark overrides. The catalog’s exact values are shown below. They are a working reference, not colors to copy into component code.

css
@theme inline {
  --color-c-black: var(--c-black);
  --color-c-white: var(--c-white);
}

:root {
  --c-black: #040404;
  --c-white: #fdfcfb;
  --secondary: #f4f2ef;
  --accent: #ece9e4;
  --muted-foreground: #5d5d5d;
  --border: color-mix(in srgb, var(--c-black) 14%, transparent);
  --input: color-mix(in srgb, var(--c-black) 18%, transparent);
}

Hairlines

--border · ink at 14%

Structural rules and secondary separators.

--input · ink at 18%

A slightly firmer edge for interactive fields.

Opacity and contrast

Ink opacity sets a readable floor; the rungs below it carry no text and are decorative only.

Technical detail

Readable floor
With the catalog palette, 60% ink measures approximately 5.50:1 on light paper and 7.15:1 on dark — the minimum for readable text.
Decorative
The 30% and 15% rungs fail normal-text contrast; use them for non-text accents only.
  • Ink

    100%

    Primary content and persistent state

  • Supporting

    /70

    Comfortable secondary copy

  • Readable floor

    --dim-1 · 60%

    Small-text minimum and first dim step

  • Decorative

    --dim-2 · 30%

    Sibling de-emphasis; never readable text

  • Hairline

    --dim-3 · 15%

    Receding marks; never readable text

At or above the floor — correct

Full ink — primary reading text.

/70 ink — supporting copy.

/60 ink — the readable minimum.

Full, /70, and /60 ink all clear AA for body text.

Below the floor — avoid

Specimen: /30 and /15 ink fail normal-text contrast. Keep those rungs for non-text marks — dimming text does not make it refined.

Companion tints

Eight optional, host-defined tints can distinguish a status dot, tag border, hairline, or faint local wash. They are not readable text colors and never become brand fields. Package components address them with an ink fallback, so a host can omit the palette without losing meaning.

  • Clay

    --tint-clay

    Light #b08968 · dark #c29a78

    Error mark.

  • Sand

    --tint-sand

    Light #c9bba8 · dark #a8987f

    Faint local wash.

  • Sage

    --tint-sage

    Light #8a9a82 · dark #9dab92

    Success mark.

  • Taupe

    --tint-taupe

    Light #a89b8c · dark #8e8275

    Neutral divider or small mark.

  • Rose

    --tint-rose

    Light #c6a9a5 · dark #c7a8a4

    Low-emphasis local accent.

  • Ochre

    --tint-ochre

    Light #b8a06a · dark #c6b078

    Warning mark.

  • Slate

    --tint-slate

    Light #7c8ba1 · dark #8fa0b2

    Information mark.

  • Moss

    --tint-moss

    Light #7e7a5f · dark #94906f

    Low-emphasis neutral mark.

css
/* Host-defined values; pair every light value with a dark value. */
:root { --tint-sage: #8a9a82; }
html[data-theme="dark"] { --tint-sage: #9dab92; }

/* Meaning stays complete when the optional tint is absent. */
.status-mark { color: var(--tint-sage, currentColor); }
.status-mark::before { background-color: currentColor; }

/* Tailwind mapping is also host-owned. */
@theme inline { --color-tint-sage: var(--tint-sage); }

Self-dimming interaction

Try the theme control

Hover and active reduce presence from full ink to the 60% floor. Focus remains visibly outlined. If a larger composition dims to 30% or 15%, only its decorative region may move down that ladder; readable labels remain at 60% or stronger.

Theme inversion

The persistent theme control in the page navigation setsdata-theme="dark" on the root and stores the choice in localStorage. A pre-paint script applies that choice before hydration, avoiding a flash of the wrong paper.

Components do not carry a theme variant. The token layer reverses ink, paper, and the supporting surfaces once; every component that inherits those roles follows automatically.

Rules

The interface draws itself in ink, paper, their opacity ladder, and a few neutral surfaces. Saturated color has one home.

Photography is the only source of saturated color.
  • Draw the interface in semantic ink and paper, never literal hex or RGB values.

  • Keep readable text at full, /70, or /60 ink; lower rungs are decorative only.

  • Use companion tints as muted local marks, not text, atmosphere, or brand fields.

  • Exchange the semantic pair with .dark-section instead of ad-hoc dark-mode overrides.

  • Let saturation come from photography; interface surfaces stay neutral.

  • Avoid Tailwind palette colors and shadows in package component code.