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.
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 | 'center' | 'justify' | 'left' | 'right' | '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.