Stepwise UI

Typography

A semantic type scale built on Inter Display. Every variant locks in size, weight, letter spacing, and line height - no decisions needed at the call site.

Installation

npx stepwise-ui add typography

This brings the typeface too. Four weights of Inter Display - 400, 500, 600, 700 - land in fonts/, with the @font-face rules in components/stepwise/fonts.css and Inter's OFL licence alongside them. The scale is drawn against this family - its -0.03em tracking assumes Inter Display's tight default spacing.

The font

Two lines wire it up. Import the face rules, then point Tailwind's sans stack at the family:

/* globals.css */
@import "tailwindcss";
@import "../components/stepwise/fonts.css";

:root {
  --font-sans: "Inter Display", ui-sans-serif, system-ui, sans-serif;
}

Using a different typeface instead: skip both lines and delete fonts/. Nothing in the library hard-codes a family - every component inherits whatever your page sets, so your own type flows through the same scale untouched. Only the tracking is tuned for Inter Display; on a wider face, relax it:

/* Your own family - no Stepwise font needed */
:root {
  --font-sans: "Your Typeface", ui-sans-serif, system-ui, sans-serif;
}

/* The scale's tracking assumes Inter Display's tight spacing.
   On a wider face, dial it back: */
.text-balance { letter-spacing: -0.01em; }

Import

import { Text } from '@/components/stepwise/typography'

Type scale

VariantPreview
hero64px / Semibold

Consistent by default

headline48px / Semibold

Consistent by default

h136px / Semibold

Consistent by default

h1-soft36px / Medium

Consistent by default

h230px / Semibold

Consistent by default

h2-soft30px / Medium

Consistent by default

h324px / Semibold

Consistent by default

h3-soft24px / Medium

Consistent by default

h420px / Semibold

Consistent by default

h4-soft20px / Medium

Consistent by default

h518px / Semibold
Consistent by default
h5-soft18px / Medium
Consistent by default
h616px / Semibold
Consistent by default
h6-soft16px / Medium
Consistent by default
body16px / Semibold

Consistent by default

body-soft16px / Regular

Consistent by default

detail14px / Regular

Consistent by default

caption13px / Medium
Consistent by default
caption-soft13px / Regular
Consistent by default
mono14px / Regular
Consistent by default

Usage

Headings

Consistent by default

Ship faster, look better

Every detail matters

<Text variant="h1">Consistent by default</Text>
<Text variant="h2">Ship faster, look better</Text>
<Text variant="h3">Every detail matters</Text>

Soft weight

Every heading has a -soft counterpart at medium (500) weight instead of semibold (600). Same size, lighter feel.

SemiBold - default weight

Medium - reduced weight

{/* SemiBold - default emphasis */}
<Text variant="h2">Consistent by design</Text>

{/* Medium - reduced weight, same size */}
<Text variant="h2-soft">Consistent by design</Text>

Body text

body

Stepwise UI gives you a consistent type scale with zero decisions at the call site.

body-soft

Stepwise UI gives you a consistent type scale with zero decisions at the call site.

detail

Supporting copy, descriptions, and secondary information in your UI.

{/* SemiBold body - for emphasis in copy */}
<Text variant="body">
  Stepwise UI gives you a consistent type scale.
</Text>

{/* Regular body - default reading weight */}
<Text variant="body-soft">
  Stepwise UI gives you a consistent type scale.
</Text>

{/* 14px supporting text */}
<Text variant="detail">
  Supporting copy and secondary information.
</Text>

Labels

captioncaption-soft
<Text variant="caption">Labels, metadata, timestamps</Text>
<Text variant="caption-soft">Secondary label text</Text>

Monospace

Uses Geist Mono. Renders as <code> by default.

const stepwise = () => "consistent"
<Text variant="mono">const stepwise = () =&gt; "consistent"</Text>

The as prop

Separates visual style from semantic HTML. Use it to keep your heading hierarchy correct for SEO and screen readers without sacrificing design - a common need when one section visually calls for an h1 but structurally should be an h2.

Visual h1, semantic h2

{/* Visual h1 style on a semantic h2 - correct for SEO */}
<Text variant="h1" as="h2">Page section title</Text>

{/* Styled timestamp with correct semantic element */}
<Text variant="caption" as="time" dateTime="2025-01-01">
  Jan 1, 2025
</Text>

{/* Form label with correct semantic element */}
<Text variant="body-soft" as="label" htmlFor="email">
  Email address
</Text>

CSS classes

The exact Tailwind classes applied per variant. Use them directly when you need the style without the component.

VariantApplied classes
herotext-[36px] md:text-[48px] lg:text-[64px] font-semibold tracking-[-0.03em] leading-none text-balance
headlinetext-[28px] md:text-[38px] lg:text-[48px] font-semibold tracking-[-0.03em] leading-none text-balance
h1text-[26px] md:text-[30px] lg:text-[36px] font-semibold tracking-[-0.03em] leading-tight text-balance
h1-softtext-[26px] md:text-[30px] lg:text-[36px] font-medium tracking-[-0.03em] leading-tight text-balance
h2text-[22px] md:text-[26px] lg:text-[30px] font-semibold tracking-[-0.03em] leading-tight text-balance
h2-softtext-[22px] md:text-[26px] lg:text-[30px] font-medium tracking-[-0.03em] leading-tight text-balance
h3text-[20px] md:text-[22px] lg:text-[24px] font-semibold tracking-[-0.03em] leading-snug text-balance
h3-softtext-[20px] md:text-[22px] lg:text-[24px] font-medium tracking-[-0.03em] leading-snug text-balance
h4text-[18px] md:text-[20px] font-semibold tracking-[-0.03em] leading-snug text-balance
h4-softtext-[18px] md:text-[20px] font-medium tracking-[-0.03em] leading-snug text-balance
h5text-[16px] md:text-[18px] font-semibold tracking-[-0.03em] leading-snug text-balance
h5-softtext-[16px] md:text-[18px] font-medium tracking-[-0.03em] leading-snug text-balance
h6text-[15px] md:text-[16px] font-semibold tracking-[-0.03em] leading-normal text-balance
h6-softtext-[15px] md:text-[16px] font-medium tracking-[-0.03em] leading-normal text-balance
bodytext-[16px] font-semibold tracking-[-0.02em] leading-relaxed text-pretty
body-softtext-[16px] font-normal tracking-[-0.02em] leading-relaxed text-pretty
detailtext-[14px] font-normal tracking-normal leading-relaxed text-pretty
captiontext-[13px] font-medium tracking-normal leading-normal text-pretty
caption-softtext-[13px] font-normal tracking-normal leading-normal text-pretty
monotext-[14px] font-normal tracking-normal leading-relaxed [font-family:var(--font-geist-mono,monospace)]

Default elements

The HTML element each variant renders when no as prop is provided.

VariantElement
hero<h1>
headline<h1>
h1<h1>
h1-soft<h1>
h2<h2>
h2-soft<h2>
h3<h3>
h3-soft<h3>
h4<h4>
h4-soft<h4>
h5<h5>
h5-soft<h5>
h6<h6>
h6-soft<h6>
body<p>
body-soft<p>
detail<p>
caption<span>
caption-soft<span>
mono<code>

API reference

TypographyVariant

type TypographyVariant =
  | 'hero'     | 'headline'
  | 'h1'       | 'h1-soft'
  | 'h2'       | 'h2-soft'
  | 'h3'       | 'h3-soft'
  | 'h4'       | 'h4-soft'
  | 'h5'       | 'h5-soft'
  | 'h6'       | 'h6-soft'
  | 'body'     | 'body-soft'
  | 'detail'
  | 'caption'  | 'caption-soft'
  | 'mono'

Props

PropTypeDefaultRequired
variantTypographyVariant"body"No

Visual style to apply. Maps to size, weight, tracking, and line height.

asElementTypeper variantNo

HTML element to render. Overrides the default without changing the visual style - essential for correct heading hierarchy and semantic markup.

childrenReactNode-No

Content to render inside the element.

classNamestring-No

Additional Tailwind classes merged via cn(). Applied after variant classes - use to override color, margin, or width.

...restHTMLAttributes-No

Any valid HTML attribute for the rendered element (id, onClick, data-*, aria-*, etc.).