Derive a full UI palette from one or two brand colours — state variants, brand-tinted greys, semantic colours. Use when building or expanding a colour system.
日本語の概要は準備中です。原文の説明を表示しています。
Ratio-based type scale where every size relates to the others. Use when defining font sizes or type tokens, or when typography feels arbitrary.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Typography feels cohesive when all font sizes are related to each other through a single mathematical ratio. Without a scale, sizes get picked arbitrarily and the result feels visually noisy — headings that don't contrast enough, body text too close in size to captions, labels that blend into content.
A modular scale starts from a base size and multiplies or divides by a ratio to generate every size in the system.
size(n) = base × ratio^n
Every size is thus a deliberate step away from the base — not a guess.
| Ratio | Name | Feel | Good for |
|---|---|---|---|
| 1.067 | Minor Second | Very tight | Dense data UIs, dashboards |
| 1.125 | Major Second | Subtle | Long-form reading, editorial |
| 1.200 | Minor Third | Balanced | Most UI applications |
| 1.250 | Major Third | Clear hierarchy | Marketing, landing pages |
| 1.333 | Perfect Fourth | Strong contrast | Display, hero sections |
| 1.414 | Augmented Fourth | Dramatic | Portfolios, branding |
| 1.500 | Perfect Fifth | Very dramatic | Use sparingly |
Default recommendation: 1.25 (Major Third) — enough contrast between steps to feel intentional without being theatrical.
Recover an existing scale, don't reverse-engineer it by hand (dembrandt engine, optional). If a brand already has type on the web,
get_typographyreturns the real font sizes, weights, and line-heights computed off the live DOM — infer the underlying ratio from those, then regularise it with the method below, instead of guessing which sizes were intended. Seeextract-design.
Starting from base = 16px, ratio 1.25:
| Step | Formula | Value | Rounded | Role |
|---|---|---|---|---|
| -2 | 16 ÷ 1.25² | 10.24px | 10px | Caption, label-xs |
| -1 | 16 ÷ 1.25 | 12.80px | 13px | Label, small |
| 0 | 16 | 16px | 16px | Body (base) |
| +1 | 16 × 1.25 | 20px | 20px | Body-lg, lead |
| +2 | 16 × 1.25² | 25px | 25px | H4 |
| +3 | 16 × 1.25³ | 31.25px | 31px | H3 |
| +4 | 16 × 1.25⁴ | 39.06px | 39px | H2 |
| +5 | 16 × 1.25⁵ | 48.83px | 49px | H1 |
| +6 | 16 × 1.25⁶ | 61.04px | 61px | Display |
Round to whole pixels or rem — the ratio provides the intent, exact pixel rounding is fine.
:root {
--text-xs: 0.625rem; /* 10px — caption */
--text-sm: 0.813rem; /* 13px — label */
--text-base: 1rem; /* 16px — body */
--text-lg: 1.25rem; /* 20px — lead */
--text-xl: 1.563rem; /* 25px — h4 */
--text-2xl: 1.938rem; /* 31px — h3 */
--text-3xl: 2.438rem; /* 39px — h2 */
--text-4xl: 3.063rem; /* 49px — h1 */
--text-5xl: 3.813rem; /* 61px — display */
}
// tailwind.config.js
fontSize: {
'xs': ['0.625rem', { lineHeight: '1rem' }],
'sm': ['0.813rem', { lineHeight: '1.25rem' }],
'base': ['1rem', { lineHeight: '1.5rem' }],
'lg': ['1.25rem', { lineHeight: '1.75rem' }],
'xl': ['1.563rem', { lineHeight: '2rem' }],
'2xl': ['1.938rem', { lineHeight: '2.25rem' }],
'3xl': ['2.438rem', { lineHeight: '2.5rem' }],
'4xl': ['3.063rem', { lineHeight: '1.1' }],
'5xl': ['3.813rem', { lineHeight: '1' }],
}
Without a scale, designers and developers pick sizes by eye or habit (14px, 16px, 18px, 24px, 32px, 48px). These feel subtly wrong because the intervals are uneven — the jump from 14→16 is small, 32→48 is large, and there is no underlying logic tying them together. The eye senses the inconsistency even when the viewer cannot name it.
With a modular scale, every size step carries the same visual weight of change. Hierarchy reads clearly because each level is a proportional step away from the next, not an arbitrary gap.
Body text base: 16px minimum. This is the browser default for good reason — it is the threshold below which reading comfort drops significantly, especially on screens.
In the modular scale, this means the base (step 0) should be 16px, and negative steps (step -1, step -2) should be used only for genuinely secondary content — never for body copy or primary labels.
The 1% heuristic. Sub-16px text is the rare exception (target: under ~1% of a page, never below 14px). Nobody counts characters, so apply it as a role test:
Check: scan one screen, count distinct sub-16px roles. Two or three from the whitelist is healthy; five or more — or any reading content — means you've over-shrunk. That's layout density, not a type problem: cut what's shown rather than shrink the type to fit.
Size and ratio set the structure; these details determine whether the type actually reads well on screen.
0. Adding it to running text slows reading and makes the type feel loose.0.04em, and reach for that only when the label genuinely needs air.≤ 0.04em) on uppercase labels — never a blanket value. Over-tracking reads as dated, not premium.An all-caps or descender-free label leaves the bottom of its line box empty. Equal margins above and below it measure equal in the inspector and read larger below. Measure gaps from the visible glyphs, not the box, and shave a few pixels from the lower gap until the two look equal. This is what people mean when spacing is "off" but they cannot say why.
Light text on dark appears optically thinner — a halation effect where bright type bleeds into the dark field. Step up one weight to compensate: where regular (400) works on light, use medium or semibold for the equivalent text on dark. This keeps perceived weight consistent across modes instead of dark-mode text looking frail.
Monospace is for technical content where character alignment matters — code, IDs, numeric tables, diffs. Don't use it as a default UI typeface, and avoid monospace + uppercase (even widths plus tall capitals are hard to scan).
A counter, a countdown, a price that updates, a column of figures — proportional digits give each number a different width, so the text jitters sideways on every tick and columns fail to line up. Switch to tabular figures anywhere digits update or stack; leave body text proportional, where tabular digits look gappy.
.stat, .price, td.numeric { font-variant-numeric: tabular-nums; }
Headings are the place to control the break. text-wrap: balance evens the lines of a short heading and stops the one-word last line; text-wrap: pretty only prevents the orphan and is the cheaper choice for paragraphs. Balance is capped at a handful of lines by the browser, so it is a heading tool, not a body tool.
Set it once, in the stylesheet, for every element of that kind. A
text-balance utility sprinkled on one component holds for that component and
silently stops holding on the next page someone builds.
h1, h2, h3, h4, h5, h6,
figcaption, blockquote, dt { text-wrap: balance; }
p, li, dd { text-wrap: pretty; }
All three browsers balance; only Chrome and Safari do pretty. Neither is
load-bearing. Balance stops at six lines (Chrome) or ten (Firefox), so never on
body copy. Titles, toasts, tooltips, captions and wrapping buttons strand words
too: add them by class to the same rule. Balance moves breaks, not the box, so
cap a left-aligned heading with max-width in ch first.
Never break a line by hand with <br> to fix a widow. It is correct at exactly
one viewport width and wrong at every other. Per-component wrapping is a
deliberate exception a designer asks for, not a default you reach for.
Bind a word to what follows it with a non-breaking space — 10 kg, Figure 3, a name and its title. A unit stranded alone on the next line reads as a typo.
text-wrap: balance fixes a heading that breaks awkwardly. It does not fix text
that is simply too long for the space it was put in, and reaching for it there
hides the real problem for one viewport width and returns at the next.
When a line orphans a word, or a caption wraps to three lines beside a title, there are exactly three honest fixes:
The order matters. Reach for the layout when the first two have run out, not before: widening a container to rescue a sentence that should have been three words moves the same problem to the next breakpoint.
What is not a fix: leaving it, or nudging the container until it happens to fit the width you are looking at.
A legend is not a sentence. "Bar height is the total, the filled part is how many completed" is thirteen words in a header sized for three. It wraps, and it strands a word on the last line. Two swatches and one word each carry the same meaning and cannot wrap badly:
■ completed □ total
The same applies to axis labels, table headers, chip text, and any caption sitting next to something larger. If the explanation genuinely needs a sentence, it belongs under the element rather than beside it.
A label with an explanation under it is two jobs, and they have to look like two jobs. Given the same size, weight and colour they merge into one block of grey that reads as neither:
<!-- wrong: identical styling, both clipped -->
<div class="w-36">
<div class="text-sm font-medium text-gray-500 truncate">Imported</div>
<div class="text-sm font-medium text-gray-500 truncate">rows without a matching account are skipped</div>
</div>
Separate them by weight and ink, not by size — this is the exception to "different jobs step apart" above: both lines already sit near the 14px floor, so dropping the second a step pushes it under the minimum, and two steps apart is barely visible at these sizes anyway. Semibold primary over medium muted is enough, and the muted colour needs a floor of its own: pair the palette's faintest legible colour with the lightest weight (400) and it reads as illegible even where it passes WCAG contrast on colour alone, because contrast math is colour-only and ignores stroke weight. Give any muted/secondary text token a minimum weight of 500 wherever it is used, not just here.
Do not truncate an explanation. A name survives clipping because a reader
recognises it from its first characters. A sentence does not: what is cut is the
part that carried the meaning, and moving it into a title attribute hides it
from touch, from keyboards, and from anyone who does not think to hover — this
is why the title-attribute escape hatch under Line Clamping below is scoped to
supporting descriptions the user does not need to act on, never to an
explanation that carries the meaning of what it is attached to. Truncate
identifiers. Let explanations wrap, shorten them, or give the column more room.
Landing pages and marketing surfaces benefit from large, expressive type — steps +4 to +6 for headlines create drama and brand presence.
Feature pages and application UI should use a more controlled range — steps +2 to +3 for headings, with body text at step 0. Oversized headings inside a functional UI distract from the content and make the layout feel unbalanced.
Match the ratio and scale usage to the purpose of the surface, not just the brand.
Four steps per view. Within one view and one context, a panel, an overlay, a page region, use no more than four steps of the scale. Four is not a taste threshold, it is the number of type roles a single view has: title, body, label, metadata. The cap follows from the roles, so a view that seems to need a fifth step is really carrying a fifth role nobody has named, and naming it usually reveals it belongs to a different view or to a group the layout has not drawn yet. Every additional size is also a claim about hierarchy, and past four the eye stops ranking them, so adjacent steps read as mistakes rather than levels. This is the same argument the Rule of Three below makes about heading levels; this one is about type sizes on screen, and a view can hit its four-size limit with a single heading level in it.
A successful heading scale uses more than just font size to distinguish levels. It also respects the cognitive limits of the page.
If headings only differ by small increments of size, they become hard to distinguish at a glance. Use these tools to create a more meaningful scale:
text-transform: uppercase) for small, lower-level headings (H4–H5) to give them visual weight without needing large sizes.letter-spacing (≤ 0.04em) — and only when the heading genuinely needs the air. Keep it subtle; over-tracking reads as dated, not premium.Most well-designed pages require only three levels of heading hierarchy (H1, H2, H3).
information-architecture).A page that needs 6 levels of headings is a page that most users will stop reading.
Typography is not just about size — it is about the rhythm and structure of the content.
For optimal reading comfort, keep body text between 45–75 characters per line (approx. 500–700px).
Line length, line-height (leading) and font size are not three independent knobs — they are one system. This is a foundational typographic principle (Bringhurst's The Elements of Typographic Style) repeatedly validated on digital platforms, and it underpins the line-height ramps in the Material and Apple HIG type scales.
The longer the measure, the more leading the eye needs to track from the end of one line back to the start of the next. A tight line-height that reads fine on a narrow column becomes tiring on a wide one.
| Measure | Recommended body line-height |
|---|---|
| Narrow (~45ch) | 1.4 |
| Comfortable (55–66ch) | 1.5 |
| Wide (~75ch) | 1.6–1.7 |
Practical rules:
In grids, cards, or lists with unpredictable content lengths, clamp text to keep a consistent visual rhythm and equal-height cards. Limit descriptions to 2–3 lines so all cards in a row stay the same height.
Multi-line clamp. Pair the standard line-clamp with the -webkit- fallback — the legacy -webkit-box form is still required for full browser support, so keep both.
.card-description {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
overflow: hidden;
}
Single-line clamp. For titles and labels that must never wrap, use the ellipsis pattern instead.
.card-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
Inside flex or grid. Truncation silently fails when the item can't shrink below its content width. Add min-width: 0 (or min-inline-size: 0) to the clamped child so it is allowed to narrow.
Accessibility — never lose the content. Clamping hides text visually only; the full string stays in the DOM and is read in full by screen readers, but a sighted user can no longer see it. Always give them a path to the rest:
title attribute (or accessible tooltip) carrying the full text, orThis escape hatch is for supporting descriptions the user does not need to act on. It does not extend to an explanation whose meaning is in the clipped part — see "Do not truncate an explanation" above.
Never clamp text that the user must read to act (prices, errors, legal copy, primary instructions) — clamp only supporting descriptions where truncation is safe.
Fixed-size pages. In a document built from fixed pages with overflow: hidden (print, PDF, A4 preview), text appended to a page the renderer already filled from measured content lands below the fold and prints on no page at all, with no error. Generated or authored copy gets a page of its own with a box bounded above the footer, and the clamp is applied in the browser where the wrapping happens. A character budget cannot predict where a page ends; only the layout engine knows.
Use specific typographic roles to provide context and guide the user through the story:
| Role | Visual Treatment | Purpose |
|---|---|---|
| Pre-title (Eyebrow) | Small (12–13px), often all-caps, subtle letter-spacing (≤ 0.04em), muted colour | Provides context or category without distracting from the main heading |
| Heading | Large, bold, modular scale step +3 to +5 | The primary hook or subject |
| Ingress (Lead text) | Larger than body (step +1), slightly bolder or higher line-height | Summarises the core value; bridging the heading and the body copy |
| Body | Base size (16px), regular weight, comfortable line-height (1.5) | The primary reading experience |
The scale compresses on smaller viewports by tightening the ratio, not by manually overriding individual sizes. The floor (body, labels) stays stable — readability has a hard minimum. The ceiling (H1, display) shrinks significantly. Every heading level scales proportionally because the ratio changes; hierarchy stays internally coherent.
Rule: use a wider ratio on desktop (more drama), a tighter ratio on mobile (more restraint). The base stays the same. The top end gives way.
Never shrink the scale from the bottom. Body text at 16px is already a floor — compression always comes from the top.
clamp()Instead of stepping sizes at fixed breakpoints, let the top end scale smoothly between a floor and a ceiling using CSS clamp(MIN, PREFERRED, MAX). The viewport-relative middle term does the scaling; the floor and ceiling stop it from ever getting too small or too large.
:root {
--text-body: 1rem; /* the floor stays fixed */
--text-h1: clamp(2rem, 1.5rem + 3vw, 3.5rem);
}
MIN is the mobile size, the MAX is the desktop size — the same floor/ceiling thinking as the stepped scale, just interpolated.clamp() to the top of the scale (headings, display). Keep body and labels at a fixed size — readability has a hard minimum, so the floor must not move.rem term in the preferred value (e.g. 1.5rem + 3vw, not 3vw alone) so the text still responds to user zoom and root font-size — a pure vw value breaks zoom accessibility.If a design system fluidizes several small steps anyway (text-fluid-xs, text-fluid-sm, and so on) rather than keeping them fixed, two adjacent clamped sizes are not guaranteed to stay apart across the viewport range. Each clamp() interpolates on its own slope between its own floor and ceiling, so two steps with different vw coefficients can land close together — or land on the exact same computed value — at some width in between, even though they look correctly spaced at the narrowest and widest extremes. Checking only the two endpoints hides this: the gap that matters is the minimum gap across the whole range, not the gap at either end.
/* Looks fine at 375px and 1440px. At ~860px both resolve to ~14.9px. */
--text-fluid-xs: clamp(0.75rem, 0.65rem + 0.4vw, 0.875rem);
--text-fluid-sm: clamp(0.8125rem, 0.7rem + 0.45vw, 0.9375rem);
clamp() for the top of the scale, where one heading rarely sits pixel-adjacent to another size on the same line.clamp() expressions) and confirm neither crosses nor closes to within a couple of px anywhere in between, not only at the breakpoints you happened to check.vw coefficients (e.g. derived from the same modular ratio) keeps their curves parallel instead of letting independently-chosen slopes cross.Hints, metadata, captions and HUD labels are read too, so they default to the same 16px as body copy. Build hierarchy with weight, colour and spacing first and go down the scale only when those are spent; secondary text never drops below 14px. Each step below base is a deliberate exception, not the default for anything called secondary.
0.04em and used only when air is needed?--text-body, --text-h1) or step (--text-base, --text-2xl), not by raw pixel value?text-wrap: balance set once, for headings and short-copy classes, and off body copy?title/tooltip or detail view), and is must-read content never clamped?clamp()) is used, is it applied only to the top of the scale, with a rem-based preferred term so zoom still works?xs/sm), do they stay visibly distinct across the whole viewport range, not just at the MIN/MAX extremes?| Anti-pattern | Problem | Fix |
|---|---|---|
| Sizes like 14, 15, 16, 17px used side by side | Steps too small to read as distinct levels | Use at minimum a 1.125 ratio so each step is perceptible |
| Arbitrary sizes with no relationship (13, 18, 27, 36px) | No underlying logic — hierarchy feels accidental | Regenerate from a single base and ratio |
| Pixel values hard-coded in components instead of tokens | Scale changes require hunting through every file | Define once as CSS custom properties or design tokens |
| Same scale used for display headings and dense data tables | One ratio rarely serves both extremes well | Use a tighter ratio (1.125) for data, wider (1.25–1.333) for marketing contexts |
| Letter-spacing added to running body text | Loosens the type and slows reading | Keep body tracking at 0; add at most 0.04em to uppercase labels when air is needed |
| Regular-weight white text on a dark background | Halation makes it look thin and frail | Step up one weight (medium/semibold) for light-on-dark text |
| Monospace as a default UI font, or monospace + uppercase | Hard to scan, reads as "unstyled" | Scope monospace to code/IDs/numeric data only |
Adjacent small steps both set with independent clamp() values | Their curves can converge or cross mid-viewport, even when the MIN/MAX extremes look fine | Keep adjacent small/label steps at a fixed size; reserve clamp() for the top of the scale |
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Derive a full UI palette from one or two brand colours — state variants, brand-tinted greys, semantic colours. Use when building or expanding a colour system.
日本語の概要は準備中です。原文の説明を表示しています。
Top bar, app launcher, tenant and environment cue, status bar. Use when designing the persistent shell or navigation between apps behind one login.
日本語の概要は準備中です。原文の説明を表示しています。
Product visuals must show the real product with real data. Use when building landing pages, hero sections, screenshots or demo panels.
日本語の概要は準備中です。原文の説明を表示しています。
Shape language across type, radius and icons carries brand personality. Use when starting a design system, picking icons or radius tokens, or reviewing visual consistency.
日本語の概要は準備中です。原文の説明を表示しています。
Rest, hover, active, focus, disabled and loading states, derived from the base colour. Use when designing buttons, links, inputs or any clickable component.
日本語の概要は準備中です。原文の説明を表示しています。
Rebuild a live web page 1:1 from measurement into Figma, Penpot or code, then verify it. Use when reproducing a page or checking a rebuild against the original.
日本語の概要は準備中です。原文の説明を表示しています。