This document defines the visual language of Modulate products for web. Its core is a combination of tokens and components, and the system itself is UI-library-agnostic. The HTML/CSS here is a reference — usable as-is in any vanilla stack. The Next.js implementation lives in Storybook.
| Layer | What it is | Developer’s rule |
|---|---|---|
Component |
A styled UI element, static or interactive. | Treat this as a reference — implement idiomatically for your stack. |
Token |
A named CSS variable storing a single property value. | Use tokens as-is: don’t rename, don’t alias, don’t generalize. |
Beyond this, the design system covers color, layout, page composition, graphics, and animation.
Color lives in two layers. The palette holds raw color values — a single source of truth, independent of theme. Semantic tokens (text, background, UI) reference palette entries and are the only place themes switch values. In CSS, point to a semantic token when available; reach into the palette only when there is no semantic match.
Raw colors. Click a swatch to copy the
var(--m__color-*) reference.
Semantic tokens. These flip with theme; the palette values they point to do not.
text
text-caption
text-accent
text-inverted
text-link
text-link-hover
text-link-pressed
text-code
text-code-accent
bg
bg-surface
bg-input
bg-accent
bg-accent-hover
bg-accent-pressed
bg-highlight
border
error
error-hover
success
success-hover
chart-default
chart-model-1
chart-model-2
chart-model-3
chart-status-success
chart-status-server-error
chart-status-client-error
chart-status-processing
emotion-neutral-group
neutral, unknown
emotion-attack-rejection-group
angry, contemptuous, disgusted
emotion-threat-uncertainty-group
afraid, anxious, stressed, surprised, ashamed, frustrated
emotion-calm-grounded-group
calm, confident, interested
emotion-excited-engaged-group
affectionate, amused, excited, happy, hopeful, proud, relieved, curious
emotion-low-energy-negative-group
sad, disappointed, bored, tired, concerned, confused
Each variable stores a full color value, for example:
rgb(253, 47, 75). To apply transparency, or to blend a
color toward the background, use color-mix() directly in
selector properties — this keeps the token list short and makes the
intent explicit in the code.
color: var(--m__bg-accent);
color: color-mix(in srgb, var(--m__bg-accent) 50%, transparent);
color: color-mix(in srgb, var(--m__bg-accent) 50%, var(--m__bg));
The main unit is rem, used for text sizes and spacing;
em is reserved for inline elements.
:root currently sets font-size: 118%, which
gives 1rem ≈ 18.9px. This can be tuned more precisely
later, including with separate values for desktop and mobile if needed.
The system supports two layout modes: desktop and mobile. Media queries look at viewport width rather than device type.
Pages are composed of stacked full-width sections,
Navigation is an exception — it can live outside the section flow and does not count as part of it.
Section content is either a Widget or Text-content. A widget is an interactive element or a dedicated component for presenting data. It's normally presented as a card with rounded corners.
Text-content is free-form HTML with shared styles applied.
A section may contain: one or several Widgets, or one Text-content block (which always spans the full width).
| Component | Sample | Tokens |
|---|---|---|
.m__widget |
--m__widget-radius--m__widget-padding
|
|
| Text-content |
This is for documentation-style copy with
links, inline
|
Gaps (--m__gap-*) are the distances
inside components — the spacing between an icon and its label,
between items in a list, or the inset padding rhythm. Reach for these
when building a component.
Besides the shared scale below, individual components carry their own
gap tokens (--m__gap-checkbox,
--m__gap-toggle,
--m__gap-table-toolbar and so on) for spacing
tuned to that component. They are not part of the general scale.
Components carry a default bottom margin (--m__default-margin-bottom) and no top margin. This keeps vertical stacking predictable and makes
it easy to top-align elements placed side by side.
Spacing between sections or before headings is handled with explicit
spacer elements (.m__space, backed by
--m__spacer-*), not margins on content
elements. The idea behind this is that content elements can’t know what
spacing is appropriate in every context — that’s why the spacer should
be placed by whoever assembles the layout.
Internal padding for UI elements grouped by category — controls, inputs,
sidebar links, etc. Most are two-value tokens
(padding-block padding-inline); some are full four-value
shorthands (top right bottom left) where the inset is asymmetric.
Independent from the spacing scale above.
Five corner radii: a four-step scale and a pill for fully-rounded shapes.
For squircle corners that scale with the element's font size, use the
.m__rounded utility instead — it sets
--m__rounded-radius (1.5em) with
corner-shape: squircle, and falls back to an SVG mask where
corner-shape is unsupported.
Three stroke weights: s for hairlines and dividers,
m for outlined controls (toggles, checkboxes, inputs,
outline buttons), l for accent frames like the upload
plate.
Font sizes use rem units except
font-size-smaller, which is em — it sizes
content slightly smaller than the surrounding text.
Sizes
Weights
Families
In this repository, the supported text elements (headings, paragraphs,
links, lists, inline code) are styled through their HTML tags. In
production, especially when combining different versions or styling
systems, that approach can let styles leak inward or
Common responses include:
The same elements show up inside interactive components and inside widgets, and on their own as unstructured HTML in Text-content sections.
| Component | Sample | Tokens |
|---|---|---|
p |
High-performance API that transforms spoken audio into sharp, actionable insights: detecting fraud, surfacing social toxicity, and spotlighting positive human behavior with impressive speed and precision. |
|
strong |
High-performance API that transforms spoken audio into sharp, actionable insights: detecting fraud, surfacing social toxicity, and spotlighting positive human behavior with impressive speed and precision. |
|
a |
License: Proprietary |
|
code |
Authenticate via the |
--m__font-size-smaller--m__font-family-mono--m__font-weight-regular--m__text-code
|
h1.loud |
Documentation |
--m__font-family-gothic--m__font-size-display
|
h1 |
Modulate Platform API (0.1.0) |
|
h2 |
List all jobs associated with the account |
|
ul |
|
|
.caption |
Supported formats: MP3, WAV, OGG, FLAC. Maximum file size 500 MB. |
--m__font-size-s--m__text-caption
|
.m__code-block |
|
--m__bg-surface--m__radius-m--m__font-family-mono--m__font-size-smaller
|
| Component | Sample | Tokens |
|---|---|---|
.m__button-primary.M |
--m__padding-button-m--m__radius-m--m__gap-xxs--m__button-icon-size--m__bg-accent--m__bg-accent-pressed--m__text-inverted--m__text-link-hover
|
|
.m__button-primary.S |
--m__padding-button-s--m__radius-s--m__gap-xxs--m__button-icon-size--m__bg-accent--m__bg-accent-pressed--m__text-inverted--m__text-link-hover
|
|
.m__button-secondary.M |
--m__padding-button-m--m__radius-m--m__gap-xxs--m__button-icon-size--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__button-secondary.S |
--m__padding-button-s--m__radius-s--m__gap-xxs--m__button-icon-size--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__button-primary-outline.M |
--m__padding-button-m--m__radius-m--m__border-width-m--m__gap-xxs--m__button-icon-size--m__font-weight-medium--m__bg-accent--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__button-primary-outline.S |
--m__padding-button-s--m__radius-s--m__border-width-m--m__gap-xxs--m__button-icon-size--m__font-weight-medium--m__bg-accent--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__button-secondary-outline.M |
--m__padding-button-m--m__radius-m--m__border-width-s--m__gap-xxs--m__button-icon-size--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__button-secondary-outline.S |
Cancel |
--m__padding-button-s--m__radius-s--m__border-width-s--m__gap-xxs--m__button-icon-size--m__text--m__text-link-hover--m__text-link-pressed
|
.m__button-danger-outline.M |
--m__padding-button-m--m__radius-m--m__border-width-s--m__error--m__error-hover
|
|
.m__button-danger-outline.S |
--m__padding-button-s--m__radius-s--m__border-width-s--m__error--m__error-hover
|
|
.m__button-success-outline.M |
--m__padding-button-m--m__radius-m--m__border-width-s--m__success--m__success-hover
|
|
.m__button-success-outline.S |
--m__padding-button-s--m__radius-s--m__border-width-s--m__success--m__success-hover
|
|
.m__menu-button-primary.M |
--m__padding-button-m--m__radius-m--m__border-width-m--m__gap-xxs--m__font-weight-medium--m__bg-accent--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__menu-button-primary.S |
--m__padding-button-s--m__radius-s--m__border-width-m--m__gap-xxs--m__font-weight-medium--m__bg-accent--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__menu-button-secondary.M |
--m__padding-button-m--m__radius-m--m__border-width-s--m__gap-xxs--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__menu-button-secondary.S |
--m__padding-button-s--m__radius-s--m__border-width-s--m__gap-xxs--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__menu-button-icon.M |
--m__radius-s--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__menu-button-icon.S |
--m__radius-s--m__text--m__text-link-hover--m__text-link-pressed
|
|
.m__nav-menu-item |
--m__gap-nav-menu-item--m__padding-nav-menu-item--m__padding-nav-menu-caption--m__padding-nav-menu-caption-first--m__radius-l--m__text--m__text-caption--m__bg-surface--m__font-weight-heavy--m__font-size-s--m__duration-base--m__duration-instant--m__easing-base
|
|
.m__nav-menu |
--m__gap-nav
|
|
.m__split-button |
--m__padding-button-m--m__radius-m--m__border-width-m--m__border-width-s--m__bg-accent--m__text-link-hover
|
|
.m__radio-card |
--m__radio-card-padding--m__border-width-s--m__radius-s--m__font-weight-regular--m__line-height-base--m__text--m__text-caption--m__text-link-hover--m__text-link-pressed--m__bg-accent-hover
|
|
.m__radio-card-group |
--m__gap-xxs
|
|
.m__option-list |
--m__padding-button-m--m__radius-m--m__gap-xs--m__gap-xxs--m__border-width-s--m__bg--m__text--m__text-link-hover
|
|
.m__tag |
Admin Internal |
--m__padding-control-xs--m__radius-xs--m__font-size-s--m__font-weight-medium--m__border-width-s
|
.m__tag-flat |
default |
--m__font-size-xs--m__font-weight-medium--m__radius-s--m__text-caption
|
.m__chip |
--m__padding-control-xs--m__radius-s--m__font-size-s--m__font-weight-medium--m__border-width-s--m__bg--m__bg-accent--m__text--m__text-link-hover
|
|
.m__chip-group |
|
--m__gap-xxs
|
.m__textfield.M |
|
--m__padding-input-m--m__radius-s--m__font-size-s--m__font-weight-medium--m__bg-input--m__border-width-s--m__border--m__text--m__text-caption--m__text-link-hover--m__bg-accent-hover--m__error--m__line-height-base
|
.m__select.M |
|
--m__padding-input-m--m__radius-s--m__font-size-s--m__font-weight-medium--m__bg-input--m__border-width-s--m__border--m__text--m__text-caption--m__text-link-hover--m__bg-accent-hover--m__line-height-base--m__bg-surface
|
.m__select.S |
--m__padding-input-s--m__radius-s--m__font-size-s--m__font-weight-medium--m__bg-input--m__border-width-s--m__border--m__text--m__text-caption--m__text-link-hover--m__bg-accent-hover--m__line-height-base--m__bg-surface
|
|
.m__datepicker.M |
|
--m__padding-input-m--m__radius-s--m__font-size-s--m__font-weight-medium--m__bg-input--m__border-width-s--m__border--m__text--m__text-caption--m__text-link-hover--m__bg-accent-hover--m__line-height-base
|
.m__datepicker.S |
--m__padding-input-s--m__radius-s--m__font-size-s--m__font-weight-medium--m__bg-input--m__border-width-s--m__border--m__text--m__text-caption--m__text-link-hover--m__bg-accent-hover--m__line-height-base
|
|
.m__segmented-control-primary |
--m__padding-button-s--m__radius-s--m__border-width-m--m__border-width-s--m__font-weight-medium--m__text--m__text-link--m__text-link-hover
|
|
.m__segmented-control-secondary |
--m__padding-button-s--m__radius-s--m__border-width-s--m__font-weight-medium--m__text--m__text-link-hover
|
|
.m__alert-error.m__alert-success
|
This reset link is invalid or has expired.
Password reset successfully.
|
--m__padding-button-m--m__radius-m--m__error--m__success
|
.m__checkbox-primary
|
--m__radius-xs--m__border-width-s--m__border--m__bg--m__bg-accent--m__bg-accent-hover--m__text--m__text-link-hover--m__gap-checkbox
|
|
.m__checkbox-secondary
|
--m__radius-xs--m__border-width-m--m__bg--m__bg-accent--m__font-weight-medium--m__text--m__text-link-hover--m__gap-checkbox
|
|
.m__toggle-primary
|
--m__radius-pill--m__border--m__bg--m__bg-accent--m__text--m__text-link-hover--m__gap-toggle
|
|
.m__toggle-secondary
|
--m__radius-pill--m__border-width-m--m__border-width-s--m__bg--m__bg-accent--m__font-weight-medium--m__text--m__text-link-hover--m__gap-toggle
|
|
.m__loader |
--m__loader-size
|
|
.m__tooltip |
Hover me to see the tooltip |
--m__color-gray-950--m__color-gray-50--m__padding-control-xs--m__radius-s--m__font-size-xs
|
.m__popover |
|
--m__bg--m__bg-surface--m__border--m__border-width-s--m__widget-radius--m__radius-m--m__font-size-xs--m__text-caption--m__text-link--m__text-link-hover--m__gap-popover-item
|
Icons are collected into one hidden SVG sprite and then reused through
<use>. This keeps each icon defined once, avoids
duplicated SVG markup, and makes color inheritance and updates easier to
manage.
Here are the current icons and their ids. A tile click copies a standalone SVG with resolved colors for Illustrator, Figma, or similar tools.
UI icons — used for interactive controls:
Brand icons and selected signal icons (for example
#deepfake, #emotions, and #music)
keep fixed accent fills where needed; other shapes use
currentColor and inherit color from the parent.
Charts — see post.
Animation tokens define timing only. The system provides tokens for instant hover, animated unhover, and shared easing, but it does not prescribe which CSS properties should be included in a transition.
Hover the row to play.
Hover the row to play at 1s.
| Interaction | Sample | Tokens |
|---|---|---|
hover |
--m__duration-instant--m__duration-base--m__easing-base
|