Typography
buttery's type roles, the fonts behind them, and the Text and TextStack primitives that set them.
A type role is a named size, line height, weight, tracking and color, such as label or title-md. Use a role for every piece of text, through Text, so text lines up from screen to screen and follows density and theme.
Set text with Text
Text renders text in the role its variant names, "label" by default. Block roles, such as title, body and caption, render a p; the rest render a span. With asChild it styles the element you give it, which is how a heading keeps its level and takes a role:
import type { ReactElement } from "react";
import { Text } from "@butteryui/core";
export function SectionTitle(): ReactElement {
return (
<Text variant="title-md" asChild>
<h2>Payouts</h2>
</Text>
);
}A block role trims its line box to the text, so the gap you set between two pieces of text is the gap you see. Body text wraps at 60 characters and display headings at 22, which keeps lines readable at any width.
Three more props cover the cases a role doesn't:
| Prop | What it does |
|---|---|
tone | Changes the color: strong, default, secondary, tertiary or disabled. Without it, text takes its role's color |
truncate | Keeps text to one line with an ellipsis and, when the text is a string, puts the whole of it in a tooltip |
tabular | Gives numbers that change equal widths, so a counter doesn't shift its neighbors |
The roles
Interface roles have one size at every width:
Display roles
Page and marketing headings use display roles, which step up in size as their container grows, at 640px and 1024px:
Stacks of text
TextStack sets a title over body text and an optional caption, with the spacing a dialog, an empty state, a page section or a feature intro uses. size picks the stack: dialog (the default), empty, section or feature.
Make it yours
Pick a backdrop, round the corners and add depth. Every change shows up right away.
Tip: hold Shift for finer steps.
Your canvas is empty
Start a fresh card, or open one from your library.
Fonts
buttery sets text in Inter Variable, bundled with @butteryui/core so desktop apps work offline, and code in the system's monospace font. Until Inter loads, a local Arial adjusted to Inter's metrics stands in, so text doesn't move when the font swaps.
Text
When to use it
Use it for
- Every piece of UI text: labels, titles, body copy, captions, display headings
Not for
- Raw font sizes or colors in CSS: pick the role that fits instead
Examples
Roles
A dialog's title, body and caption, each in its role.
Make it yours
Pick a theme, choose a background and set the fonts. Every change shows up right away.
Tip: press T to try the next theme.
Truncated label
An inspector row whose label truncates and keeps its full text in a tooltip, beside a value in tabular figures.
Type scale
The app's roles from a page title down to a caption, each in its own color, and the tones that override it.
title-lg
Quarterly review
title-md
Themes
title
Your canvas is empty
label
Slide sizebody
Shared with the team on Tuesday, presented on Friday.
body-sm
Add a slide, or open a deck from your library.
caption
Edited 4 min ago
Variants and states
Roles
Every role on the type scale; display roles scale with their container.
title-lg
Pick a theme for every slide
title-md
Pick a theme for every slide
title
Pick a theme for every slide
dialog-title
Pick a theme for every slide
title-sm
Pick a theme for every slidelabel
Pick a theme for every slidebody
Pick a theme for every slide
body-sm
Pick a theme for every slide
button
Pick a theme for every slidebutton-lg
Pick a theme for every slidecontrol
Pick a theme for every slidemenu
Pick a theme for every slidebadge
Pick a theme for every slidecaption
Pick a theme for every slide
kbd
Pick a theme for every slidedisplay
Pick a theme for every slide
heading-1
Pick a theme for every slide
heading-2
Pick a theme for every slide
body-lg
Pick a theme for every slide
Tones
Truncation
One line with an ellipsis; the full text is in the title.
Props
Text
| Prop | Type | Default | Description |
|---|---|---|---|
ref | Ref<HTMLElement> | The rendered element: a p, a span, or the child with asChild. | |
variant | TextVariant | "label" | The role on the type scale. Defaults to "label". Block roles (titles, body, captions, display roles)
render a p and trim their line box to the text, so the gap above and below is the gap you set.
The rest render a span. |
tone | "strong" | "default" | "secondary" | "tertiary" | "disabled" | Color override. Defaults to the role's own color. | |
truncate | boolean | false | Keep to one line and end with an ellipsis. When the children are a string they also become the
title, so the full text stays available. Defaults to false. |
tabular | boolean | false | Tabular figures, for numbers that change. A tabular number keeps its width in a row instead of
shrinking under its neighbors. Defaults to false. |
asChild | boolean | Render the single child element (such as an h2) instead, with Text's props merged onto it. |
It also takes Omit<ComponentPropsWithRef<"span">, "ref">.
Accessibility
Renders a p or span; use asChild to render a heading (h1鈥揾6) so the document outline stays correct. Truncated text keeps the full text in its title.
It takes no keyboard input of its own.
Motion
It doesn't move.
With reduced motion: Nothing changes.
Layout
Block roles (title-lg, title-md, title, dialog-title, body, body-sm, caption and the display roles) are paragraphs trimmed to their text, with body text wrapping at 60 characters and display headings at 22; the other roles are inline spans. With truncate it keeps to one line and ends in an ellipsis, with its full text in its title. It has no hit area.
Related
TextStack
When to use it
Use it for
- A dialog's or an onboarding step's title, body and tip
- An empty state's title and description (size empty, centered)
- A page section's heading and lede (size section)
Not for
- A single line of text: use Text
- A label and its value in a row: put them in a row with Inline
Examples
Empty copy
A panel's empty state, centered, with its title as a paragraph because the panel already has its heading.
Saved decks
Nothing saved yet
Decks you save show up here, ready to present or share.
Sizes
The four sizes: a dialog's copy with its tip, an empty state, a page section's heading and lede, and a feature intro.
dialog
Make it yours
Pick a theme, choose a background and set the fonts.
Tip: press T to try the next theme.
empty
Your canvas is empty
Add a slide, or open a deck from your library.
section
Every deck, one place
Find any deck in seconds and pick up where you left off.
feature
Present from any screen
Open a deck on the room's screen and keep your notes on your laptop.
Step copy
The copy of one onboarding step: a title, what it does, and a tip.
Make it yours
Pick a theme, choose a background and set the fonts. Every change shows up right away.
Tip: press T to try the next theme.
Variants and states
Dialog
A dialog title, the body 16px below and a caption 20px below that, measured between the trimmed text.
Make it yours
Pick a theme, choose a background and set the fonts. Every change shows up right away.
Tip: press T to try the next theme.
Empty state
A title and its description 12px below, centered.
Your canvas is empty
Add a slide, or open a deck from your library.
Page section
A section heading over larger body text.
Every deck, one place
Find any deck in seconds and pick up where you left off.
Props
TextStack
| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | ReactNode | The heading. | |
body | ReactNode | The text under the title. | |
caption | ReactNode | A hint under the body, such as a shortcut tip. | |
size | "dialog" | "empty" | "section" | "feature" | "dialog" | "dialog" (the default): a dialog title, its body 16px below and a caption 20px below that.
"empty": an empty state's title with its description 12px below. "section": a page section's
heading over larger body text, 16px apart. "feature": a title-lg heading over body text, 12px
apart, as a feature intro or an announcement opens. |
align | "start" | "center" | "start" | "start" (the default) or "center", as in empty states, which also balances the lines. |
titleAs | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "h2" | What the title renders as: a heading level, or "p" when it isn't a heading. Defaults to "h2". |
It also takes Omit<ComponentPropsWithRef<"div">, "title" | "children">.
Accessibility
The title is a real heading, an h2 unless titleAs says otherwise, so the stack shows up in a screen reader's list of headings; pick the level that fits the page. Use titleAs="p" when the title isn't a heading. The body and caption are paragraphs.
It takes no keyboard input of its own.
Motion
It doesn't move.
With reduced motion: Nothing changes.
Layout
It's as wide as its parent. The size sets the gaps, measured between trimmed text: a dialog's body sits 16px under its title and the caption 20px under that, an empty state's 12 and 12, a section's 16 and 16, and a feature's 12 and 16. Text wraps, body text at 60 characters, and it has no hit area.
Related
Related
- Colors
The color tokens every component draws with, by role, in light and dark.
- Spacing and sizing
The 4px spacing scale, the control heights that change with density, and the widths pages and panels keep to.