Sets paragraphs, headings and links on the library's type scale, so a screen of your own reads like the components around it. Reach for it wherever you would otherwise style text by hand.
import { Text, Heading, Link } from "@umriss-ui/core";
A heading's outline level and its size are independent: choose the level for the page's structure, the size for the eye.
Text renders a p; inside a line, render it as a span.
A heading, a paragraph and a link in it, each at its default: the base of every surface.
import { Heading, Link, Stack, Text } from "@umriss-ui/core";
export default function TextHeadingLink() {
return (
<Stack gap={2}>
<Heading level={3}>Checkout</Heading>
<Text>
Takes card and wallet payments for every booking. Owned by the Payments team;{" "}
<Link href="#/typography">see the on-call rota</Link>.
</Text>
</Stack>
);
}
Six steps from xs to 2xl; md is body text, sm and xs carry dense tables, hints and timestamps.
import { Stack, Text } from "@umriss-ui/core";
export default function Sizes() {
return (
<Stack gap={2}>
<Text size="2xl">2xl · Q1 budget review</Text>
<Text size="xl">xl · Marketing is 12 % over plan</Text>
<Text size="lg">lg · Cost centre CC-1200</Text>
<Text size="md">md · Actuals are booked until the end of February.</Text>
<Text size="sm">sm · Forecast updated by Rafael Ortiz</Text>
<Text size="xs">xs · Last booking 17/03/2026 10:30</Text>
</Stack>
);
}
Step text back with tone rather than a colour of your own; weight has three steps, medium for emphasis inside a line. A Heading is semibold unless its weight says otherwise - medium for a quieter one.
import { Heading, Stack, Text } from "@umriss-ui/core";
export default function TonesAndWeights() {
return (
<Stack gap={2}>
<Heading level={3} size="md">
Morning dispatch
</Heading>
<Heading level={4} size="md" weight="medium">
Tours today
</Heading>
<Text weight="semibold">Tour T-03 · North depot</Text>
<Text>
14 stops, <Text as="span" weight="medium">2 running late</Text>
</Text>
<Text size="sm" tone="secondary">
Driver Martin Hale · van FP 214 K
</Text>
<Text size="xs" tone="muted">
Position reported 3 minutes ago
</Text>
</Stack>
);
}
Set figures and codes mono: tabular digits keep amounts in a column aligned, and 0 and O stay apart.
import { Grid, Text } from "@umriss-ui/core";
const ROWS = [
{ centre: "CC-1100 Sales", actual: "138,412.00" },
{ centre: "CC-1200 Marketing", actual: "76,190.50" },
{ centre: "CC-2100 Engineering", actual: "181,007.25" },
{ centre: "CC-4400 Facilities", actual: "9,880.00" },
];
export default function Numbers() {
return (
<Grid columns={2} gap={2} style={{ maxWidth: 360 }}>
{ROWS.flatMap((row) => [
<Text key={`${row.centre}-name`} size="sm">
{row.centre}
</Text>,
<Text key={`${row.centre}-actual`} size="sm" mono style={{ textAlign: "right" }}>
{row.actual}
</Text>,
])}
</Grid>
);
}
Use tracking="caps" for small letter-spaced labels and leading="tight" for key figures and labels that run over two lines.
import { Stack, Text } from "@umriss-ui/core";
export default function TrackingAndLeading() {
return (
<Stack gap={3} style={{ maxWidth: 280 }}>
<Text size="xs" tracking="caps" tone="muted">
Sprint 14 · Web team
</Text>
<Text size="2xl" tracking="display" leading="tight" weight="semibold">
38 of 52 points
</Text>
<Text size="sm" leading="tight" tone="secondary">
Checkout flow redesign and member portal sign-up, carried over from sprint 13
</Text>
</Stack>
);
}
Pick level for the page's outline and size for the eye; a small card title can still be a second-level heading.
import { Heading, Stack, Text } from "@umriss-ui/core";
export default function LevelAndSize() {
return (
<Stack gap={3}>
<Heading level={2} size="sm" tone="secondary">
Open incidents
</Heading>
<Heading level={3} size="xl">
INC-1048 · Checkout slow, card payments time out
</Heading>
<Text size="sm" tone="secondary">
A screen reader lists "Open incidents" as the section and the incident inside it, whatever
their sizes.
</Text>
</Stack>
);
}
Set external for a link that leaves the application: it opens a new tab and cuts the new page's access to this one.
import { Link, Text } from "@umriss-ui/core";
export default function ExternalLink() {
return (
<Text size="sm">
Card payments time out at the provider.{" "}
<Link href="https://status.example.org" external>
Provider status page
</Link>{" "}
· <Link href="#/typography">Incident INC-1048</Link>
</Text>
);
}
Text wraps and headings balance their lines; where one line is a must, clip it with CSS of your own and keep the whole value in title.
import { Heading, Stack, Text } from "@umriss-ui/core";
const CUSTOMER = "Holloway Garden Supplies & Landscaping Wholesale, Unit 14, Riverside Trading Estate";
const REFERENCE = "SH-1042/FP214K/2026-03-17T10:30:00Z/attempt-2";
export default function LongValues() {
return (
<Stack gap={3} style={{ maxWidth: 320 }}>
<Heading level={4}>{CUSTOMER}</Heading>
<Text size="sm" mono style={{ overflowWrap: "anywhere" }}>
{REFERENCE}
</Text>
<Text
size="sm"
tone="secondary"
title={CUSTOMER}
style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}
>
{CUSTOMER}
</Text>
</Stack>
);
}
The pieces together in a detail panel: a caps label, a heading, mono identifiers and times, body text, a muted footnote and links.
import { Badge, Card, CardBody, Heading, Link, Stack, Text } from "@umriss-ui/core";
/* Data from the operations world, written out here so the example runs on its own. */
const at = (day: number, hours: number, minutes = 0) => new Date(2026, 2, day, hours, minutes).getTime();
interface Service {
id: string;
name: string;
team: string;
/** 1 is customer-facing and pages at night; 3 waits for the morning. */
tier: 1 | 2 | 3;
/** The latency objective: the 95th percentile stays below this, in ms. */
latencySlo: number;
/** The availability promised for a month, in per cent. */
availabilityTarget: number;
}
const SERVICES: readonly Service[] = [
{ id: "checkout", name: "Checkout", team: "Payments", tier: 1, latencySlo: 300, availabilityTarget: 99.95 },
{ id: "billing", name: "Billing", team: "Payments", tier: 1, latencySlo: 400, availabilityTarget: 99.9 },
{ id: "sign-in", name: "Sign-in", team: "Identity", tier: 1, latencySlo: 200, availabilityTarget: 99.95 },
{ id: "search", name: "Search", team: "Discovery", tier: 1, latencySlo: 250, availabilityTarget: 99.9 },
{ id: "images", name: "Image service", team: "Discovery", tier: 2, latencySlo: 500, availabilityTarget: 99.5 },
{ id: "notifications", name: "Notifications", team: "Messaging", tier: 2, latencySlo: 800, availabilityTarget: 99.5 },
{ id: "webhooks", name: "Webhooks", team: "Integrations", tier: 2, latencySlo: 1000, availabilityTarget: 99.5 },
{ id: "reports", name: "Reporting", team: "Insights", tier: 3, latencySlo: 2000, availabilityTarget: 99 },
];
interface Engineer {
id: string;
name: string;
team: string;
}
const ENGINEERS: readonly Engineer[] = [
{ id: "priya", name: "Priya Raman", team: "Payments" },
{ id: "jonas", name: "Jonas Keller", team: "Payments" },
{ id: "ada", name: "Ada Mwangi", team: "Identity" },
{ id: "tomasz", name: "Tomasz Nowak", team: "Discovery" },
{ id: "leila", name: "Leila Haddad", team: "Discovery" },
{ id: "sam", name: "Sam Okafor", team: "Messaging" },
{ id: "ines", name: "Ines Duarte", team: "Integrations" },
{ id: "felix", name: "Felix Brandt", team: "Insights" },
];
interface Incident {
id: string;
title: string;
service: string;
severity: "SEV1" | "SEV2" | "SEV3";
opened: number;
acknowledged?: number;
resolved?: number;
/** An engineer's id. */
assignee: string;
}
const INCIDENTS: readonly Incident[] = [
{ id: "INC-1048", title: "Checkout slow, card payments time out", service: "checkout", severity: "SEV1", opened: at(17, 9, 42), acknowledged: at(17, 9, 46), assignee: "jonas" },
{ id: "INC-1047", title: "Webhook deliveries delayed", service: "webhooks", severity: "SEV3", opened: at(17, 7, 15), acknowledged: at(17, 8, 2), assignee: "ines" },
{ id: "INC-1046", title: "Thumbnails missing for new uploads", service: "images", severity: "SEV2", opened: at(16, 22, 5), acknowledged: at(16, 22, 11), resolved: at(17, 0, 40), assignee: "tomasz" },
{ id: "INC-1045", title: "Sign-in codes arrive late", service: "sign-in", severity: "SEV2", opened: at(16, 14, 20), acknowledged: at(16, 14, 24), resolved: at(16, 15, 5), assignee: "ada" },
{ id: "INC-1044", title: "Monthly report export fails", service: "reports", severity: "SEV3", opened: at(15, 10, 0), acknowledged: at(16, 9, 12), resolved: at(16, 11, 30), assignee: "felix" },
{ id: "INC-1043", title: "Search returns no results for some regions", service: "search", severity: "SEV1", opened: at(14, 18, 30), acknowledged: at(14, 18, 33), resolved: at(14, 19, 55), assignee: "leila" },
{ id: "INC-1042", title: "Duplicate reminder e-mails", service: "notifications", severity: "SEV3", opened: at(13, 8, 45), acknowledged: at(13, 9, 30), resolved: at(13, 13, 0), assignee: "sam" },
{ id: "INC-1041", title: "Invoices generated twice", service: "billing", severity: "SEV2", opened: at(12, 16, 10), acknowledged: at(12, 16, 18), resolved: at(12, 18, 40), assignee: "priya" },
];
const incident = INCIDENTS[0]!;
const service = SERVICES.find((s) => s.id === incident.service)!;
const assignee = ENGINEERS.find((e) => e.id === incident.assignee)!;
const time = (t: number) =>
new Date(t).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
export default function IncidentSummary() {
return (
<Card>
<CardBody>
<Stack gap={3}>
<Stack direction="row" gap={2} align="center">
<Text size="xs" tracking="caps" tone="muted">
{service.team} · {service.name}
</Text>
<Badge tone="danger">{incident.severity}</Badge>
</Stack>
<Heading level={3} size="lg">
{incident.title}
</Heading>
<Text size="sm">
Opened <Text as="span" mono>{time(incident.opened)}</Text>, acknowledged{" "}
<Text as="span" mono>{time(incident.acknowledged!)}</Text> by {assignee.name}. The p95
latency is above its {service.latencySlo} ms objective since 09:40.
</Text>
<Text size="sm">
<Link href="#/typography">Open the latency chart</Link> ·{" "}
<Link href="https://status.example.org" external>
Payment provider status
</Link>
</Text>
<Text size="xs" tone="muted">
<Text as="span" size="xs" mono>
{incident.id}
</Text>{" "}
· still open
</Text>
</Stack>
</CardBody>
</Card>
);
}
| Key | Action |
|---|---|
Tab |
Reaches each Link; Text and Heading are not stops. |
Enter |
Follows the link; an external one opens in a new tab. |
TextProps| Name | Type | Default | Description |
|---|---|---|---|
as | ElementType | "p" | Element to render as. Shown in: Tones and weights, Set an incident summary, Use the language in your own components (Language) and 17 more |
size | TextSize| "xs" | "md" | Type size from the token set. It is independent of the element: a p
may be small without becoming a different element.Shown in: Configurator, Sizes, Tones and weights and 129 more |
weight | TextWeight| "regular" | "regular" | Font weight from the token set. Shown in: Configurator, Tones and weights, Tracking and leading and 15 more |
tone | TextTone| "default" | "default" | How loud the text is. secondary and muted step back without
falling below the contrast threshold.Shown in: Configurator, Tones and weights, Tracking and leading and 94 more |
mono | boolean | false | Geist Mono with tabular figures – for numbers and identifiers. Shown in: Configurator, Numbers and identifiers, Handle long values and 31 more |
tracking | TextTracking| "normal" | "normal" | Letter spacing from the token set. caps belongs with letterspaced
capitals.Shown in: Configurator, Tracking and leading, Set an incident summary |
leading | TextLeading"normal" | "tight" | "normal" | Line spacing. tight for multi-line headings and key figures, where
the normal spacing lets the lines fall apart.Shown in: Configurator, Tracking and leading |
ref | Ref<HTMLElement> | — | Receives the component's root element. from RefPropShown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more |
Also takes every attribute of the rendered element.
HeadingProps| Name | Type | Default | Description |
|---|---|---|---|
level | | 1 | 2 | The page's outline level (h1–h6). Shown in: Text, heading and link, Tones and weights, Choose the level apart from the size and 6 more |
size | TextSize| "xs" | the size matching the level | Optical size; independent of the level. Shown in: Tones and weights, Choose the level apart from the size, Set an incident summary and 4 more |
weight | TextWeight| "regular" | "semibold" | Font weight from the token set. Shown in: Tones and weights |
tone | TextTone| "default" | "default" | How loud the heading is. Shown in: Choose the level apart from the size |
ref | Ref<HTMLHeadingElement> | — | Receives the component's root element. from RefPropShown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more |
Also takes every attribute of the heading.
LinkProps| Name | Type | Default | Description |
|---|---|---|---|
size | TextSize| "xs" | "md" | Size from the token set – a link has to match its body text. |
external | boolean | false | Opens in a new tab and sets the necessary protective attributes. Shown in: Link out of the application, Set an incident summary |
ref | Ref<HTMLAnchorElement> | — | Receives the component's root element. from RefPropShown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more |
Also takes every attribute of <a>.
TextSizeA type size of the token set.
type TextSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl";TextWeightA font weight of the token set.
type TextWeight = "regular" | "medium" | "semibold";TextToneHow loud a text is; secondary and muted step back without falling below
the contrast threshold.
type TextTone = "default" | "secondary" | "muted";TextTrackingThe letter spacing of the token set.
type TextTracking = "normal" | "tight" | "display" | "caps";TextLeadingThe line height of the token set.
type TextLeading = "normal" | "tight";Link renders a plain a and knows no router; pass it the address your router resolves.What umriss deliberately does not build, and why: ADR-0032.