Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

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:

tsx

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:

PropWhat it does
toneChanges the color: strong, default, secondary, tertiary or disabled. Without it, text takes its role's color
truncateKeeps text to one line with an ellipsis and, when the text is a string, puts the whole of it in a tooltip
tabularGives numbers that change equal widths, so a counter doesn't shift its neighbors

The roles

Interface roles have one size at every width:

Pick a backdrop and round the cornerstitle-lg 路 24/30 路 600
Pick a backdrop and round the cornerstitle-md 路 18/24 路 600
Pick a backdrop and round the cornerstitle 路 15/20 路 600
Pick a backdrop and round the cornersdialog-title 路 15/20 路 600
Pick a backdrop and round the cornerstitle-sm 路 14/20 路 600
Pick a backdrop and round the cornerslabel 路 14/20 路 500
Pick a backdrop and round the cornersbody 路 14/22 路 400
Pick a backdrop and round the cornersbody-sm 路 13/20 路 400
Pick a backdrop and round the cornersbutton 路 14/20 路 500
Pick a backdrop and round the cornersbutton-lg 路 15/20 路 600
Pick a backdrop and round the cornerscontrol 路 13/18 路 500
Pick a backdrop and round the cornersmenu 路 14/20 路 400
Pick a backdrop and round the cornersbadge 路 12/16 路 500
Pick a backdrop and round the cornerscaption 路 12/16 路 400
Pick a backdrop and round the cornerskbd 路 11/14 路 500

Display roles

Page and marketing headings use display roles, which step up in size as their container grows, at 640px and 1024px:

Make anything butterydisplay 路 sm 32/38 路 md 40/44 路 lg 48/52
Make anything butteryheading-1 路 sm 24/30 路 md 28/34 路 lg 32/38
Make anything butteryheading-2 路 sm 20/26 路 md 22/28 路 lg 24/30
Make anything butterybody-lg 路 sm 16/24 路 md 17/26 路 lg 18/28

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.

Background angle for the Spring launch title slide135掳

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 size

body

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

strongdefaultsecondarytertiary

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 slide

label

Pick a theme for every slide

body

Pick a theme for every slide

body-sm

Pick a theme for every slide

button

Pick a theme for every slide

button-lg

Pick a theme for every slide

control

Pick a theme for every slide

menu

Pick a theme for every slide

badge

Pick a theme for every slide

caption

Pick a theme for every slide

kbd

Pick a theme for every slide

display

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

strongdefaultsecondarytertiary

Truncation

One line with an ellipsis; the full text is in the title.

A label far longer than the row it has to fit inside, so it truncates

Props

Text

PropTypeDefaultDescription
refRef<HTMLElement>The rendered element: a p, a span, or the child with asChild.
variantTextVariant"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.
truncatebooleanfalseKeep 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.
tabularbooleanfalseTabular figures, for numbers that change. A tabular number keeps its width in a row instead of shrinking under its neighbors. Defaults to false.
asChildbooleanRender 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.

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

PropTypeDefaultDescription
title

required

ReactNodeThe heading.
bodyReactNodeThe text under the title.
captionReactNodeA 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.

  • 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.