Skip to content

Typography

The typography components set prose and labels in the global type scale: headings, inline text, paragraphs, links, quotes, lists, separators, keycaps and eyebrow labels. Each one sets its own size, color and spacing from theme tokens, so text built from them reads the same in Brutal and Elegant, wherever it is placed.

tsx
import { Blockquote, Eyebrow, Heading, Kbd, Link, List, ListItem, Paragraph, Separator, Text, } from "raft-ui";

Block components (Heading, Paragraph, Blockquote, List, Separator) carry their own vertical margins and drop them at the start and end of their container. Inline components (Text, Link, Kbd, Eyebrow) have none.

Heading

Heading renders a section heading, h1 to h6. Its size comes only from level, taken from the global type scale, and it carries its own vertical margins.

Basic

level picks the element and its size: h1 24px, h2 20px, h3 18px, h4 to h6 16px. The default is 2.

Prefix bar

prefix="bar" draws a bar before the text. type colors the bar: default, primary, info, success, warning or error. The text keeps the primary label color.

Align text

With alignText, the bar moves into the left gutter so the heading text lines up with the body text below it.

Spacing

A heading has a margin above and below it, larger for h1 to h3. The first child of its container drops its top margin and the last child its bottom margin.

Anchor

id and other attributes go to the heading element, so a link can point at it.

Props

NameTypeDefaultDescriptionVersion
level1 | 2 | 3 | 4 | 5 | 62Renders h1 to h6, and sets the size.Unreleased
prefixbarundefinedbar draws a bar before the text.Unreleased
typedefault | primary | info | success | warning | errordefaultThe bar’s color.Unreleased
alignTextbooleanfalseWith prefix="bar", moves the bar into the left gutter so the text lines up with the body.Unreleased

Other h1 attributes (id, className, aria-*, ref …) go to the heading element.

Pass level to pick the heading. Sizes follow the raft-ui type scale, so h1 is 24px; use a display size of your own for a hero title.

Text

Text renders a run of text in the body size (--r-font-size-4 / --r-line-height-4). It always sets its own color, the primary label color unless type or depth says otherwise, so it reads the same wherever it is placed. There is no size prop.

Type

type picks a color role: default, primary, info, success, warning or error.

Depth

depth steps the default color down to the secondary and tertiary label colors. It only applies when type is default.

Strong, italic, underline, delete

delete wraps the content in <del>, so assistive tech announces it as removed.

Code

code renders the content as inline code in <code>. It keeps the color of type and depth, and combines with delete.

Element

as picks the element, from a fixed set of text elements. The element’s meaning stays; its size and color come from Text.

Props

NameTypeDefaultDescriptionVersion
asTextElementspanThe element to render: span, div, p, label, strong, em, b, i, small, time, abbr, cite, q, mark, sub, sup, dfn, legend, figcaption, dt or dd.Unreleased
codebooleanfalseRenders the content as inline code.Unreleased
deletebooleanfalseStrikes the content through with <del>.Unreleased
depth1 | 2 | 3undefinedPrimary, secondary or tertiary label color. Only with type="default".Unreleased
italicbooleanfalseItalic text.Unreleased
strongbooleanfalseThe theme’s strong weight.Unreleased
typedefault | primary | info | success | warning | errordefaultColor role.Unreleased
underlinebooleanfalseUnderlined text.Unreleased

Other props, including ref, go to the root element, which is always the as element. <code> or <del> sit inside it, so as and the type of ref always match.

Paragraph

Paragraph renders a <p> of prose: the body font size (step 4 of the type scale) with the prose line height, --r-line-height-prose (1.6), rather than the fixed 20px line height of dense UI text.

Basic

Paragraphs keep 16px above and below. The first paragraph in its container has no space above and the last has none below.

Depth

depth sets the text emphasis: 1 is the primary text color and the default, 2 and 3 are quieter.

Props

NameTypeDefaultDescriptionVersion
depth1 | 2 | 31Text emphasis: primary, secondary or tertiary label color.Unreleased

Other props, including ref, go to the <p>. A paragraph without depth uses the primary text color.

Link is an inline link inside text. It keeps the font and size of the text around it, takes its color from type, and lets the theme decide the underline.

A link reads as part of the sentence: only its color and underline change.

primary is the brand link color. neutral is a quiet gray link for meta lines and footers, where a colored link would be too loud.

auto follows the theme: Elegant underlines on hover only, Brutal always draws a 2px underline that thickens on hover. hover, always and none fix it in every theme.

target="_blank" adds rel="noopener noreferrer"; tokens you pass in rel are kept.

as renders another component instead of <a>, such as your router’s link. Link accepts that component’s props (here to) and passes its class, rel and ref on, so the component must render an <a> that takes them.

NameTypeDefaultDescriptionVersion
asElementType'a'The element or component to render, e.g. a router’s Link.Unreleased
typeprimary | neutralprimaryColor role.Unreleased
underlineauto | hover | always | noneautoWhen the underline shows; auto follows the theme.Unreleased

Other props go to the rendered element. type is the color role, so the anchor’s own type attribute is not available. type and underline defaults can be set for a subtree with ConfigProvider’s componentOptions={{ Link: { … } }}.

A visited link keeps its type’s color.

Blockquote

Blockquote renders a <blockquote> with a bar on its left, in secondary text. Like the other typography components it carries its own vertical margins and drops them at the start and end of its container.

Basic

cite and the other native attributes pass through to the <blockquote>.

Paragraphs

Paragraphs inside a quote sit closer together than on the page, and the first and last lose their outer margins so the bar fits the text.

Align text

alignText hangs the bar and its gap in the left gutter, so the quoted text lines up with the text around it.

Nested

A quote inside a quote gets its own bar.

Props

NameTypeDefaultDescriptionVersion
alignTextbooleanfalseHangs the bar in the left gutter so the text lines up with the surrounding text.Unreleased

Every other <blockquote> attribute, including cite and ref, passes through. The text color is fixed to the secondary label color. Elegant draws a thin neutral bar; Brutal draws a 2px ink bar and sets the text in italic.

List

List renders a prose list: a <ul>, or an <ol> when ordered. Each ListItem is an <li>; put another List inside an item to nest. The list carries its own vertical margin, dropped when it is the first or last thing in its container, and each item after the first sits a little below the one before.

This is the typography list. A virtualized list for long feeds is a separate, planned component.

Basic

Bullets by default, numbers with ordered. Brutal draws square bullets and bold numbers in ink; Elegant keeps them quiet in the tertiary label color.

Ordered

start sets the first number, reversed counts down and type picks the numbering (1, a, A, i, I). All three go to the <ol> and only apply with ordered.

Nested

A List inside a ListItem is a sub-list. Nested bullet lists switch to a second bullet style.

Align text

alignText removes the indent: the markers hang in the left gutter and the item text lines up with the text around the list.

Task list

A task item drops its marker so the leading checkbox stands in for it, as in a GFM task list (- [x] …). An item whose first child is a checkbox (a native <input type="checkbox"> or a Checkbox) does this on its own in browsers with :has(); task does it explicitly, and also works when the checkbox is wrapped.

Props

NameTypeDefaultDescriptionVersion
orderedbooleanfalseRenders an <ol> instead of a <ul>.Unreleased
startnumberundefinedThe number of the first item. Only with ordered.Unreleased
reversedbooleanundefinedCounts down. Only with ordered.Unreleased
type1 | a | A | i | IundefinedNumbering style. Only with ordered.Unreleased
alignTextbooleanfalseHangs the markers in the gutter so the item text aligns with the surrounding text.Unreleased

Other props go to the <ul> / <ol>.

ListItem Props

NameTypeDefaultDescriptionVersion
taskbooleanfalseDrops the marker so a leading checkbox replaces it.Unreleased

Other props, such as value, go to the <li>.

Theme tokens

TokenElegantBrutal
--r-list-font-size / --r-list-line-height--r-font-size-4 / --r-line-height-4same
--r-list-margin16px 0same
--r-list-padding2emsame
--r-list-item-margin0.25em 0 0same
--r-list-bullet-type / --r-list-nested-bullet-typedisc / circlesquare / square
--r-list-marker-font-weightregularstrong
--r-list-marker-colorinitial (tertiary label)primary label

Separator

Separator draws a line between content: a rule between blocks, a rule with a label in it, or a short vertical rule between inline items.

Basic

With no children it renders an <hr> with space above and below. As the first or last child of its container it drops that outer space.

Label

Children put a label in the line. labelPlacement moves it to the left, center or right. The label also names the separator for assistive tech.

Dashed

Vertical

orientation="vertical" renders an inline line, 1em tall, between inline items.

Decorative

decorative marks a line as purely visual: it gets role="none" and is hidden from assistive tech. A label stays readable as plain text.

Props

NameTypeDefaultDescriptionVersion
childrenReactNodeundefinedA label in the line. Ignored when vertical.Unreleased
dashedbooleanfalseDraws the line dashed.Unreleased
decorativebooleanfalsePurely visual: no separator role.Unreleased
labelPlacementleft | center | rightcenterWhere the label sits on the line.Unreleased
orientationhorizontal | verticalhorizontalA block rule or an inline rule.Unreleased

Other props go to the root element: <hr>, the labeled <div role="separator">, or the vertical <span role="separator">.

Kbd

Kbd marks up keyboard input as a keycap, using the native <kbd> element. It is sized in em, so it follows the text it sits in.

Basic

Put the key’s label inside.

Key combinations

keys renders one keycap per key, joined by separator (+ by default). As the HTML spec suggests for combinations, the keycaps are wrapped in an outer <kbd>.

Size

medium is 0.875em of the surrounding text and small is 0.75em, so keycaps scale with headings and captions alike.

Modifier symbols

Kbd does not detect the platform or map key names. Write the symbol you want, and give symbols a title so the name shows on hover.

Props

NameTypeDefaultDescriptionVersion
childrenReactNodeundefinedThe key’s label. Ignored when keys is set.Unreleased
keysreadonly ReactNode[]undefinedRenders a combination: one keycap per entry inside an outer <kbd>.Unreleased
separatorReactNode"+"What goes between the keys of a combination.Unreleased
sizesmall | mediummediumRelative to the surrounding text: 0.75em or 0.875em.Unreleased

Kbd also accepts native kbd attributes and a ref, which go to the outer element of a combination.

Eyebrow

Eyebrow is the small label above or beside a section: a list group, a panel block, a form field. It is bold, uppercase and widely spaced, always in the secondary label color, so every section label reads the same wherever it sits. Chinese text renders as is.

Basic

12px, the strong weight and the widest letter spacing. The color is set, not inherited.

Mono

mono switches to the monospace meta voice, one step smaller at 11px, for runtime details, versions and counters.

Section heading

When the eyebrow is the only title of a section, render it as a heading with as="h2" to "h6" so it shows up in the document outline. It looks the same; browser heading margins are reset.

Form label

With as="label" and htmlFor, the eyebrow labels a form control.

Before and after

before and after put an icon or a count next to the label. Counts use tabular figures.

Props

NameTypeDefaultDescriptionVersion
asspan | div | label | h2 | h3 | h4 | h5 | h6spanThe element to render. div and headings are blocks; span and label are inline.Unreleased
htmlForstringundefinedThe id of the control the label belongs to. Only used with as="label".Unreleased
monobooleanfalseMonospace at 11px instead of the sans face at 12px.Unreleased
beforeReactNodeundefinedContent before the label, such as an icon.Unreleased
afterReactNodeundefinedContent after the label, such as a count.Unreleased
childrenReactNodeundefinedThe label text.Unreleased

Eyebrow also accepts native attributes and a ref. It has no margins; space it from the layout around it. Decorative icons in before should carry aria-hidden.