Modulate Design System

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

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.

Palette

Raw colors. Click a swatch to copy the var(--m__color-*) reference.

UI Color Palette

Semantic tokens. These flip with theme; the palette values they point to do not.

text

text

text-caption

text-accent

text-inverted

text-link

text-link-hover

text-link-pressed

text-code

text-code-accent

surface

bg

bg-surface

bg-input

bg-accent

bg-accent-hover

bg-accent-pressed

bg-highlight

border

border

feedback

error

error-hover

success

success-hover

Charts

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

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.

CSS
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));

Layout

Units

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.

Page composition

Pages are composed of stacked full-width sections, not column grids. Each section always spans the full available width and takes as much height as its content needs; sections are self-contained and can be reordered without side effects. Complex layouts live inside sections, not across them.

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
This is for interactive elements and purpose-built widgets that display data.
--m__widget-radius
--m__widget-padding
Text-content

This is for documentation-style copy with links, inline code, headings, and lists, when the content is mostly unstructured HTML rather than a purpose-built UI widget.

Gaps

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.

Spacers

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.

Paddings

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.

Radius

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.

Border width

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.

Typography

Tokens

Font sizes use rem units except font-size-smaller, which is em — it sizes content slightly smaller than the surrounding text.

Sizes

Weights

Families

Blocks

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 outward.

Common responses include:

  • scoping styles at the component boundary, using class-based selectors instead of bare tags where outward leakage is a concern,
  • adding component-level CSS resets where inward leakage is a concern,
  • or another approach that fits the stack.

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 Authorization header using Basic auth.

--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
  • The username should be equal to your account ID.
  • The password should be equal to your API key.
.caption

Supported formats: MP3, WAV, OGG, FLAC. Maximum file size 500 MB.

--m__font-size-s
--m__text-caption
.m__code-block
example.yaml
endpoint: /api/velma-2-stt-batch
method: POST
curl -X POST https://api.modulate.ai \
  -H "X-API-Key: your_key"
--m__bg-surface
--m__radius-m
--m__font-family-mono
--m__font-size-smaller

Components

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
Enter a valid email address
--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

Graphics

Icons

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.

#modulate
#ai-music
#account
#api-docs
#api-key
#batch
#behaviors
#billing
#decrease
#done
#calendar
#models
#music
#overview
#redaction
#prosocial
#stt-med
#streaming
#transcript
#velma
#usage
#search
#deepfake
#emotions
#apple
#facebook
#github
#language
#google
#microsoft

UI icons — used for interactive controls:

#chevron-down
#chevron-left
#close
#checkmark
#dots

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

Charts — see post.

Animations

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.

Durations

Hover the row to play.

Easing

Hover the row to play at 1s.

In components

Interaction Sample Tokens
hover --m__duration-instant
--m__duration-base
--m__easing-base