Buttons
A button runs an action; a link drawn as a button goes somewhere. One class, btn, then a tone, a size or an icon when the button needs one.
Examples
The plain btn is neutral. btn-primary marks the one main action of a screen; the other tones say what an action does.
<button class="btn btn-primary" type="button">Save</button>
<button class="btn" type="button">Cancel</button>
<button class="btn btn-secondary" type="button">Duplicate</button>
<button class="btn btn-success" type="button">Publish</button>
<button class="btn btn-danger" type="button">Delete</button>
<button class="btn btn-warning" type="button">Withdraw</button>
<button class="btn btn-info" type="button">Preview</button>Outline, subtle, text and link
Quieter forms of the same tones for secondary actions, rows and toolbars. btn-text has no fill until hovered; btn-link reads as a link and keeps a button's size, and it alone wraps a long text.
<button class="btn btn-outline-primary" type="button">Outline</button>
<button class="btn btn-outline-danger" type="button">Outline danger</button>
<button class="btn btn-subtle-primary" type="button">Subtle</button>
<button class="btn btn-subtle-danger" type="button">Subtle danger</button>
<button class="btn btn-text" type="button">Text</button>
<button class="btn btn-link" type="button">Link</button>
<button class="btn btn-link text-start" type="button">A long link wraps inside its box, like the title of a card that opens a window</button>Sizes
A size is a word: btn-xs 24 px, btn-sm 28 px, the default 32 px, btn-lg 40 px - the heights of every control of the same size. btn-xl, 56 px, is for a touch screen and the one action of a page; in a kiosk it grows to a kiosk's size.
<button class="btn btn-primary btn-xs" type="button">Extra small</button>
<button class="btn btn-primary btn-sm" type="button">Small</button>
<button class="btn btn-primary" type="button">Default</button>
<button class="btn btn-primary btn-lg" type="button">Large</button>
<button class="btn btn-primary btn-xl" type="button">Extra large</button>Icons
An icon sits beside the words, or alone in a square btn-icon whose name is its aria-label. btn-close draws the close glyph by itself.
<button class="btn" type="button"><i class="bi bi-plus-lg" aria-hidden="true"></i>New excursion</button>
<a class="btn" href="#btn-icons">Open the site<i class="bi bi-box-arrow-up-right" aria-hidden="true"></i></a>
<button class="btn btn-icon" type="button" aria-label="Edit"><i class="bi bi-pencil" aria-hidden="true"></i></button>
<button class="btn btn-icon btn-text" type="button" aria-label="More actions"><i class="bi bi-three-dots" aria-hidden="true"></i></button>
<button class="btn btn-icon btn-sm btn-subtle-danger" type="button" aria-label="Remove"><i class="bi bi-trash3" aria-hidden="true"></i></button>
<button class="btn-close" type="button" aria-label="Close"></button>Groups and toolbars
btn-group joins buttons into one control - here a choice of view, each button pressed or not. btn-toolbar lays groups and buttons out in a wrapping row.
<div class="btn-toolbar" role="group" aria-label="List view">
<div class="btn-group" role="group" aria-label="Layout">
<button class="btn" type="button" aria-pressed="true">List</button>
<button class="btn" type="button" aria-pressed="false">Board</button>
<button class="btn" type="button" aria-pressed="false">Calendar</button>
</div>
<div class="btn-group" role="group" aria-label="Export">
<button class="btn" type="button">Export</button>
<button class="btn btn-icon" type="button" aria-label="Export options"><i class="bi bi-chevron-down" aria-hidden="true"></i></button>
</div>
</div>A choice of one
A segmented choice is Bootstrap's: in a btn-group, each input type="radio" class="btn-check" before its label class="btn". The radio stays the control - the arrow keys move the choice, the form sends it - and the checked one is drawn pressed, an outline tone filled with its colour; the focus ring goes on the label. A link or a button that is the current one of a group carries aria-current and is drawn pressed too.
<div class="btn-toolbar">
<div class="btn-group" role="radiogroup" aria-label="Period">
<input class="btn-check" type="radio" name="period" id="btn-segmented-day" value="day">
<label class="btn btn-outline-primary" for="btn-segmented-day">Day</label>
<input class="btn-check" type="radio" name="period" id="btn-segmented-week" value="week" checked>
<label class="btn btn-outline-primary" for="btn-segmented-week">Week</label>
<input class="btn-check" type="radio" name="period" id="btn-segmented-month" value="month">
<label class="btn btn-outline-primary" for="btn-segmented-month">Month</label>
</div>
<div class="btn-group" role="radiogroup" aria-label="Status">
<input class="btn-check" type="radio" name="state" id="btn-segmented-all" value="all" checked>
<label class="btn" for="btn-segmented-all">All</label>
<input class="btn-check" type="radio" name="state" id="btn-segmented-open" value="open">
<label class="btn" for="btn-segmented-open">Open</label>
<input class="btn-check" type="radio" name="state" id="btn-segmented-closed" value="closed">
<label class="btn" for="btn-segmented-closed">Closed</label>
</div>
<nav class="btn-group" aria-label="Year">
<a class="btn btn-sm" href="#btn-segmented">2025</a>
<a class="btn btn-sm" href="#btn-segmented" aria-current="page">2026</a>
</nav>
</div>btn-group-vertical stacks the buttons of a group, each as wide as the widest.
<div class="btn-group-vertical" role="group" aria-label="Order">
<button class="btn" type="button"><i class="bi bi-arrow-up" aria-hidden="true"></i>Move up</button>
<button class="btn" type="button"><i class="bi bi-arrow-down" aria-hidden="true"></i>Move down</button>
<button class="btn" type="button"><i class="bi bi-trash" aria-hidden="true"></i>Remove</button>
</div>Toggle and busy
A toggle carries aria-pressed. While its action runs, REDZ.ui.busy(button, promise) shows a spinner in the button and marks it aria-disabled: it keeps the focus and ignores clicks until the promise settles. See Busy for the words a button says while it waits.
<button class="btn" type="button" aria-pressed="false" data-docs-run="toggle"><i class="bi bi-pin-angle" aria-hidden="true"></i>Pin</button>
<button class="btn btn-primary" type="button" data-docs-run="busy-button">Save for 2 seconds</button>A wait before it works again
data-cooldown="N" makes a button wait N seconds: it is aria-disabled - it keeps the focus and ignores clicks - and shows the seconds left after its words, in the page's text cooldown; then it wakes and the attribute goes. Setting the attribute again starts the wait again; a page reloaded during a wait renders the seconds left. The count is drawn, not read out every second: say the wait in words once, when it starts - "A new code is on its way; ask again in a minute". A cooldown set while the button is busy keeps it disabled after the wait, until its count ends.
<button class="btn" type="button" data-cooldown="20" data-docs-run="cooldown"><i class="bi bi-arrow-clockwise" aria-hidden="true"></i>Send the code again</button>States
A state is the attribute accessibility needs anyway, on the button itself: no active or disabled classes. aria-expanded is set by the browser on a button that opens a menu or a dialog.
| class | rest | :hover | :focus-visible | aria-pressed | aria-expanded | disabled | aria-disabled | aria-busy |
|---|---|---|---|---|---|---|---|---|
| btn | ||||||||
| btn-primary | ||||||||
| btn-danger | ||||||||
| btn-outline-primary | ||||||||
| btn-subtle-primary | ||||||||
| btn-subtle-secondary | ||||||||
| btn-subtle-success | ||||||||
| btn-subtle-danger | ||||||||
| btn-subtle-warning | ||||||||
| btn-subtle-info | ||||||||
| btn-text | ||||||||
| btn-link | ||||||||
| btn-close | - | - | - | - |
Keyboard and accessibility
<button type="button">for an action,<a href>for a place;btndraws both the same. Enter and Space press a button, Enter follows a link.- A button with only an icon takes its name from
aria-label; the icon isaria-hidden="true". disabledtakes a button out of the tab order and hides why.aria-disabled="true"keeps it focusable - so a tooltip can say why - and the library swallows its clicks.- A button that opens a menu or a dialog uses the browser's command:
commandfor+command, neverdata-bs-toggle. - A segmented choice is radios: one Tab stop, the arrow keys move the choice, a screen reader says "1 of 3". A group of toggles that each stand alone is buttons with
aria-pressed. - The focus ring is 2 px of
--redz-focus, 3:1 against the page in both themes.
Tokens
From css/components/btn.css. It declares --redz-btn-bg, --redz-btn-fg, --redz-btn-hover, --redz-btn-hover-fg, --redz-btn-on-bg, --redz-btn-on-fg on its root, never on rows or items.
It reads the theme's --redz-bg-hover, --redz-text, --redz-control-height, --redz-space-12, --redz-radius-8, --redz-focus, --redz-bg-page, --redz-text-faint, --redz-primary, --redz-primary-contrast, --redz-secondary, --redz-secondary-contrast, --redz-success, --redz-success-contrast, --redz-danger, --redz-danger-contrast, --redz-warning, --redz-warning-contrast, --redz-info, --redz-info-contrast, --redz-primary-subtle, --redz-secondary-subtle, --redz-success-subtle, --redz-danger-subtle, --redz-warning-subtle, --redz-info-subtle, --redz-control-height-xs, --redz-radius-6, --redz-fs-12, --redz-control-height-sm, --redz-space-8, --redz-control-height-lg, --redz-space-16, --redz-fs-16, --redz-space-24, --redz-radius-12, --redz-fs-20, --redz-control-height-float, --redz-text-muted, --redz-motion.
From Bootstrap 5.3
| Bootstrap 5.3 | REDZ.UI | Change | Note |
|---|---|---|---|
btn-close-white | - | remove | the close glyph takes the text color |
btn-dark | - | review | no light or dark buttons: btn or btn-text |
btn-light | - | review | no light or dark buttons: btn or btn-text |
btn-outline-dark | - | review | btn-outline-secondary |
btn-outline-light | - | review | btn-outline-secondary |
data-bs-toggle="button" | aria-pressed | review | switch aria-pressed in your click handler |