Typography

Bold rules that create consistency across the whole component library.

Installation

Installs the shared type scale and prose stylesheet. Wrap rendered Markdown in .typeset; add .typeset-compact for compact spacing and text.

Typography is hard. Every style you add makes it clunkier, so building this page was mostly cutting and refactoring until only the essential rules remained. Those rules power every component in the library, and each one is simple. Defining them and applying them on every surface is what makes it hard. Good rules hold every kind of component, and every component holds to them. Enjoy!

5 rules

  • 5 styles for the whole site, from page titles to captions.
  • 3 weights: regular for text, semibold for headings and selected items, bold for page titles.
  • 2 text colors: foreground and muted.
  • 1 font family: Inter Variable 4.0.
  • 1 paragraph style.

5 no-gos

  • No uppercase: hierarchy comes from size and weight.
  • No letter-spacing: handled with Inter's optical sizing.
  • No eyebrows: a section starts with its title.
  • No difference with markdown: it uses the same styles and colors as everything else.
  • No opacity for text: it's foreground or muted, never a faded step of them.

The scale

5 styles, each a size and a line height. PressSfor compact.

Markdown

Markdown gets the same 5 styles, to-dos and tables included.

Inspect

Getting started with BoringUI

Flavors Radix and Base UI · Stack React and Tailwind v4 · License MIT

Setup

  • Install the type scale and the markdown styles

  • Add a dropdown and a dialog

  • Move every list to fluid hover

    • Sidebar

    • Command menu

  • Switch dense screens to compact

How it moves

Every component animates with 3 spring speeds, and hover glides to the item under your cursor instead of blinking. A label turns semibold without moving its neighbors, since fontWeights pairs each weight with an optical size. The motion page has every value.

No component invents its own timing, so everything moves at the same pace.

What you get

  • Components in 2 flavors

    • Radix

    • Base UI

  • A size ladder of 36px and 28px rows

  • A skill for your coding agent

Spring speeds

Speed

Duration

Used for

fast

0.08s

Hover, tooltips, focus rings

moderate

0.16s

Dropdowns and tabs

slow

0.24s

Dialogs and drawers

Install

Each piece installs with the shadcn CLI. We planned to ship one big bundle ship one item per component, so you only add what you use:

# One component
npx shadcn@latest add https://boringui.dev/r/dropdown.json

# The skill for your coding agent
npx skills add https://github.com/MaybeMonad/vaexu/tree/main/apps/boring-ui/skills/boring-ui --skill boring-ui -g -y

Next steps

  1. Read the craft notes for each component

  2. Install the skill

    1. Let it read your stack

    2. Fix what it flags

  3. Ship it


Found a bug? Open an issue on GitHub.

Spacing

Space only goes above a block, so streaming text never shifts.

GapDefaultCompact
Above display and title24px16px
Above subtitle20px12px
Below display, title, and subtitle8px4px
Between body blocks12px8px
Above caption8px4px
Between list items4px4px
Around a divider24px16px

5 tweaks

Small details that keep text still, snug in its box, and evenly wrapped.

Weight without reflow

Each weight carries its own optical size, so semibold labels keep their width. Idea from @lochieaxon.

Weight only
Weight + optical size

Steady numbers

Changing numbers usetabular-numsso every digit is the same width and the words after them hold still.

Installed 10 of 48 components
Proportional digits
Installed 10 of 48 components
Tabular digits

Trimmed labels

Labels usetext-box: trim-both cap alphabeticso the padding you set is the space you see. Idea from @jakubkrehel.

Draft saved
Untrimmed
Draft saved
Trimmed

Balanced headings

Headings usetext-wrap: balanceso their lines come out even.

Give every component the same motion

wrap leaves a short last line

Give every component the same motion

balance evens the lines

Pretty paragraphs

Paragraphs usetext-wrap: prettyso none ends on a lone word.

Hover glides from item to item instead of blinking, so the highlight always sits under your cursor.

wrap leaves one word alone

Hover glides from item to item instead of blinking, so the highlight always sits under your cursor.

pretty brings a word down to join it