Browse docs
Browse docs
The foundation of every readable surface. <Typography> collapses the visual scale, the semantic HTML tag, and the intent color into a single typed prop set — so every heading and every paragraph in your app reads from the same source of truth.
import { Typography } from '@dashforge/tw';
<Typography variant="h1">Workspace settings</Typography>
<Typography variant="body1" color="muted">Control access and notifications.</Typography><Typography variant="h2" gutterBottom>Profile</Typography>
<Typography variant="body1">
Update your display name, email, and notification preferences below.
</Typography>
<Typography variant="caption" color="muted">
Last updated 2 days ago
</Typography>The default variant is body1 → renders a <p>. Default color is inherit so Typography painted inside a <Box variant="solid" color="primary"> reads white without configuration.
Subtitle 1 — emphasized lead
Body 1 — primary prose paragraph for long-form reading.
Body 2 — dense secondary text for helper lines.
OVERLINE — SECTION KICKERimport { Typography, Stack } from '@dashforge/tw';
<Stack gap={2}>
<Typography variant="h2">Heading 2 — section</Typography>
<Typography variant="h4">Heading 4 — subsection</Typography>
<Typography variant="subtitle1">Subtitle 1 — emphasized lead</Typography>
<Typography variant="body1">
Body 1 — primary prose paragraph for long-form reading.
</Typography>
<Typography variant="body2" color="muted">
Body 2 — dense secondary text for helper lines.
</Typography>
<Typography variant="overline">OVERLINE — SECTION KICKER</Typography>
</Stack>Headings get font-bold (h1, h2) or font-semibold (h3–h6) by default; body and subtitle stay normal/medium; overline gets uppercase + wide tracking automatically.
Primary — calls-to-action accent
Success — saved · just now
Warning — 3 days until your trial ends
Danger — required field
Muted — last seen 4 hours ago
import { Typography, Stack } from '@dashforge/tw';
<Stack gap={1}>
<Typography color="primary">Primary — calls-to-action accent</Typography>
<Typography color="success">Success — saved · just now</Typography>
<Typography color="warning">Warning — 3 days until your trial ends</Typography>
<Typography color="danger">Danger — required field</Typography>
<Typography color="muted">Muted — last seen 4 hours ago</Typography>
</Stack>muted is the workhorse for secondary text — helper lines, captions, "last updated" labels. inherit is the escape: inside any container that paints its own color, leave Typography untouched and it reads cleanly.
The variant baked-in weight is just a default. When you want "an h2 styled like h2 but lighter":
<Typography variant="h2" weight="normal">
Subtle heading
</Typography>tailwind-merge resolves the conflict — the font-normal from weight wins over the variant's font-bold.
<div className="w-48">
<Typography truncate>
This is a very long string that will collapse to a one-line ellipsis…
</Typography>
</div>truncate enables text-ellipsis + overflow-hidden + whitespace-nowrap in one prop. For one-line WITHOUT ellipsis (rare — usually for status badges), use noWrap instead.
<Typography variant="h2" align="center" gutterBottom>
Welcome back
</Typography>
<Typography variant="body1" align="center" color="muted">
Pick up where you left off.
</Typography>gutterBottom adds mb-3 — the canonical bottom margin between a heading and its following paragraph. Mirror of MUI's same flag, familiar muscle memory.
Vertical rhythm is owned by the design system, not the browser. Every Typography variant ships with m-0 in its base classes, so the UA stylesheet's default heading margins (<h1> 0.67em, <h2> 0.83em, <p> 1em, etc.) are always neutralized — including in apps that disable Tailwind's preflight core plugin for MUI/Ant/Chakra coexistence. Reach for gutterBottom, <Stack gap>, or sx="mt-*" to place spacing explicitly.
Sometimes the heading hierarchy of the page should differ from the visual hierarchy of the section. The as prop swaps the tag while keeping the variant's visuals:
<Typography variant="h1" as="h2">
Looks like h1, but in the page outline this is an h2.
</Typography>Useful for hero sections embedded inside an article that already has its own <h1>.
asChild — paint onto a router LinkFor a styled <Link> with no extra wrapper element:
import { Link } from 'react-router-dom';
<Typography variant="h3" color="primary" asChild>
<Link to="/dashboard">Open dashboard →</Link>
</Typography>Radix Slot merges our className + ref onto the child. The rendered DOM is exactly <a> — no extra <h3> wrapping it. When both as and asChild are passed, asChild wins.
<Box variant="solid"><Box variant="solid" color="primary" rounded="2xl" p={12}>
<Typography variant="h1" color="inherit" gutterBottom>
Get started today
</Typography>
<Typography variant="body1" color="inherit">
Two minutes from install to your first component on screen.
</Typography>
</Box>color="inherit" is the magic: Box paints the surface, Typography reads the resulting color from its parent. No prop drilling, no CSS variables to remember.
Configure <Typography> defaults application-wide through the theme.
import { patchTheme } from '@dashforge/tw-theme';
patchTheme({
components: {
Typography: {
defaults: { variant: 'body1', color: 'neutral', weight: 'normal' },
},
},
});Configurable axes (TypographyVariantProps):
| Axis | Type | Notes |
|---|---|---|
variant | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'subtitle1' | 'subtitle2' | 'body1' | 'body2' | 'caption' | 'overline' | Type scale. |
color | intent role | 'inherit' | 'muted' | Semantic + inheritance option. |
weight | 'normal' | 'medium' | 'semibold' | 'bold' | Font-weight override. |
align | 'left' | 'center' | 'right' | 'justify' | Text alignment. |
truncate | boolean | One-line ellipsis. |
noWrap | boolean | One-line without ellipsis. |
gutterBottom | boolean | Add mb-3. |
Non-visual axes (as, asChild, children, event handlers, ARIA) are not theme-configurable — per-instance semantics.
Precedence chain (lowest → highest):
defaultVariants from the internal typographyVariants recipe (variant: 'body1', color: 'neutral').theme.components.Typography.defaults (application-wide).<Typography variant="h1" weight="bold" />) — wins over theme.sx — appended to the root and wins over conflicting classes via tailwind-merge.TypeScript: theme.components.Typography.defaults autocompletes to the seven axes above.
Reactivity: useComponentDefaults('Typography') subscribes to the theme store — patchTheme re-renders every mounted instance inheriting the changed axis.
| Prop | Type | Default | Description |
|---|---|---|---|
align | 'right' | 'left' | 'center' | 'justify' | 'left' | Text alignment. |
as | ElementType | — | Override the HTML tag. Defaults to a sensible mapping per variant (h1→h1, …, body1→p, caption→span, overline→span). Use when the semantic heading level should differ from the visual scale — e.g. a "hero" rendered as an <h2> but styled like h1.Ignored when asChild is true. |
asChild | boolean | — | Render via Radix Slot — the Typography styles paint onto the single React child instead of wrapping it in our own tag. Useful for <Typography asChild><Link>...</Link></Typography> to get a styled router link with no extra DOM.Mutually exclusive with as (when both are passed, asChild wins and as is ignored — see Typography.tsx for the reason). |
color | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral' | 'inherit' | 'muted' | 'neutral' | Semantic intent. 'inherit' uses the parent's text color; 'muted' renders as a lightened neutral for secondary info. |
gutterBottom | boolean | false | Adds mb-3 for the canonical heading→paragraph rhythm. |
noWrap | boolean | false | Single-line without ellipsis (whitespace-nowrap). |
sx | string | — | Utility classes appended to the variant chain. Resolved via tailwind-merge so the consumer's classes always win over the variant defaults — e.g. sx="text-pink-500" overrides color="primary". |
truncate | boolean | false | Single-line ellipsis (text-ellipsis overflow-hidden whitespace-nowrap). |
variant | 'body1' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'subtitle1' | 'subtitle2' | 'body2' | 'caption' | 'overline' | 'body1' | Type scale — drives font-size, line-height, weight, and the default HTML tag inferred by as. |
weight | 'normal' | 'medium' | 'semibold' | 'bold' | 'extrabold' | — | Overrides the variant's default font-weight. |
...rest | Omit<HTMLAttributes<HTMLElement>, 'className' | 'color'> | — | Additional native attributes forwarded via Omit<HTMLAttributes<HTMLElement>, 'className' | 'color'>. |
| Variant | Tag | Rationale |
|---|---|---|
h1–h6 | h1–h6 | Semantic heading level matches visual level by default |
subtitle1, subtitle2, body1, body2 | p | Block-level prose |
caption, overline | span | Inline annotation, no implicit block break |
<Typography> has no slots. It renders a single DOM element (tag inferred from variant, overridable via as or asChild) — there is no anatomy to target separately.
The slotProps API pattern only exists on compound components with multiple visually distinct regions. Examples: <Menu>, <DataGrid>, <Autocomplete>, <Card>.
For <Typography>, use sx for any style override, or wrap in Box when you need a richer style surface.
import { typographyVariants } from '@dashforge/tw';
// typographyVariants({ variant: 'h2', color: 'primary' }) → className stringUseful when extending the catalogue downstream (e.g. an app-level "display" variant that wraps Typography).
asChild vs as precedence: when both are passed, asChild wins. We considered making this a compile-time discriminated union, but the props type already has 8 axes — one more dimension would inflate IntelliSense suggestions for marginal benefit. Documented + tested.color="inherit" is the right default for nested usage. Typography painted inside <Box variant="solid" color="primary"> reads white because the Box sets the inherited color on its container.weight always wins over variant default — <Typography variant="h1" weight="normal"> produces a thin display heading. tailwind-merge resolves the conflicting font-* classes to the last one.lineHeight prop in v1 — the per-variant leading is opinionated (leading-[1.05] for h1 down to leading-relaxed for body). Override via sx="leading-loose" when needed. We'll consider promoting this to a prop if it becomes the most-overridden axis.<Typography>: nesting <p> inside <p> is invalid HTML. Wrap the inner text in <Typography as="span"> if you really need that.direction="row" and put icon + Typography side by side. Embedding an SVG inside Typography breaks truncate and confuses screen readers.