What is a Bootstrap button?
Take a plain button element, add .btn and one modifier class, and you have one.
The component belongs to the Bootstrap framework, so Bootstrap’s stylesheet has to be on the page. Color, hover, active and disabled styling all come from that file. By itself, .btn only handles padding and content alignment.
For a CDN setup, link the CSS from jsDelivr in the head and put bootstrap.bundle.min.js just before the closing body tag. The bundle includes Popper, which plain buttons never touch but dropdown buttons do. With npm, run npm i bootstrap@5.3.8 and either compile the Sass or import the built CSS.
The stylesheet alone covers variants, sizes and disabled buttons. Only toggle behavior needs the JavaScript bundle.
As of October 2026, the Bootstrap docs list v5.3.8 as the current release. The team’s release post from August 25, 2025 said the plan was for it to be the last patch before v5.4.0, and every class in this article targets 5.3.x.
Which elements can use the btn class?
You can put .btn on a button, an a or an input. Button is the one for actions and a is for navigation. Input only makes sense when existing form markup leaves you no choice.
| Element | Disable with | Attributes to add | Best use |
|---|
| button | disabled attribute | type=”button” or type=”submit” | Actions, form submit |
| a | .disabled class | role=”button”, aria-disabled | Navigation |
| input | disabled attribute | type and value | Fixed form markup |
The Bootstrap docs warn that some browsers render anchor and input buttons slightly differently. The classes are written for the button element first.
When an anchor triggers in-page behavior, like collapsing content, it needs role=”button”. That is an ARIA role, and it makes screen readers announce a button instead of a link.
Anchors also lack keyboard behavior out of the box. The W3C ARIA Authoring Practices example for an anchor-based button adds tabindex=”0″ because an anchor without an href gets no focus on its own.
My rule is simple. If the click changes something on the page, use a button. If it changes the URL, use an anchor.
Bootstrap button variants, outline styles and sizes
Bootstrap ships 9 button variants. Eight are theme colors (primary, secondary, success, danger, warning, info, light and dark) and the ninth is link. Each one is a modifier class paired with .btn, such as btn-success.
Solid and outline variants
Solid variants fill the background with the theme color. Outline variants, written btn-outline-*, drop the background and keep the color on the border and text.
- btn-primary for the one main action in a view
- btn-secondary or btn-outline-secondary for supporting actions, and btn-danger for anything that destroys something
A view needs one clear call-to-action, so one btn-primary per view is a sound default. That’s a design opinion, not a Bootstrap rule.
Outline buttons carry a contrast risk. The docs say styles with a light foreground, the light outline among them, belong on dark backgrounds only.
Bootstrap’s accessibility page goes further. Some default palette combinations, button variations included, can fall below the WCAG 2.2 ratios of 4.5:1 for text and 3:1 for non-text elements, mostly on light backgrounds.
Test the color contrast of every variant you ship, because the docs leave that check to you.
Link buttons
btn-link looks like a link and acts like a button. Its color follows the theme’s link color, which suits low-emphasis actions like cancel, placed beside a solid button.
Button sizes
Buttons got local CSS variables in v5.2.0, according to the Bootstrap docs, and that is what makes per-button sizing possible without extra classes.
btn-lg and btn-sm change padding and font size. For anything in between, set –bs-btn-padding-y, –bs-btn-padding-x and –bs-btn-font-size inline on the one button that needs it.
The default size needs no class. Two built-in sizes and a variable override cover most layouts.
How to disable a Bootstrap button
Add the disabled attribute to a button or input element. An anchor has no disabled attribute, so it takes the .disabled class and aria-disabled=”true” instead, and it should carry no href.
Disabled buttons and disabled anchors
On a button or input, the disabled attribute also applies pointer-events: none, so hover and active states stop firing. An anchor needs .disabled, role=”button” and aria-disabled=”true”, and no href.
Bootstrap 5.3.8 sets disabled opacity to .65 through the $btn-disabled-opacity Sass variable, per its source files.
Keeping the href on a disabled anchor is the trap. The docs state that pointer-events: none does not stop keyboard navigation, so keyboard and screen reader users can still activate the link unless you add tabindex=”-1″ and script the behavior off.
Drop the href and the anchor leaves the tab order, since the W3C example needs tabindex=”0″ to put an href-less anchor back into it.
For a real action I’d use a button with disabled and be done with it. The disabled anchor is for links that have to stay anchors.
Active and focus states
The active look is painted by –bs-btn-active-bg, –bs-btn-active-color and –bs-btn-active-border-color. The .active class forces that look without a click. The focus ring gets its color from –bs-btn-focus-shadow-rgb, which each variant sets. A bare .btn with no variant class has no focus styles at all, so the docs tell you to add your own :focus or :focus-visible rules.
Don’t strip the focus ring without putting a replacement style in its place. Keyboard users lose their spot on the page.
How do Bootstrap toggle buttons work?
Add data-bs-toggle=”button” to a button and each click flips its .active class. The button plugin does the flipping, so the JavaScript bundle must be loaded.
With no .active class the button looks unpressed. To start it pressed, add .active and aria-pressed=”true” in the markup yourself. Add disabled as well and the toggle stays inert.
Pre-pressed buttons need both attributes. Without aria-pressed, assistive technology reads the button as unpressed.
W3C’s ARIA Authoring Practices Guide says to pick one signal. Keep the label fixed and let aria-pressed carry the state, or change the label and skip aria-pressed.
Bootstrap’s toggle examples follow the first pattern. A label that swaps from Mute to Unmute should not also carry aria-pressed.
Toggle button or checkbox toggle?
They look the same on screen but screen readers treat them differently. Plugin toggles are announced as “button” and “button pressed”, while checkbox toggles built with btn-check are announced as checked or not checked, according to the Bootstrap docs.
Use a checkbox toggle when the control is a form value, and a radio group for one-of-many choices. For a saved preference such as notifications, a Bootstrap toggle switch usually reads better than either.
Scripts can flip a button too. bootstrap.Button.getOrCreateInstance(element).toggle() does it.
How to group, align and stretch Bootstrap buttons
A joined row of buttons goes in .btn-group, and several groups sit inside .btn-toolbar. Full-width stacks use .d-grid with .gap-2. Alignment and centering come from flex and spacing utilities rather than button classes.
Button groups and toolbars
.btn-group joins buttons or links on one line. .btn-toolbar holds several groups, spaced with utilities such as me-2.
Size belongs on the group, not on each button. .btn-group-lg and .btn-group-sm resize everything inside, though the class has to be repeated on each nested group. For a column instead of a row, .btn-group-vertical stacks the buttons.
Each group needs role=”group” (or role=”toolbar” for a toolbar) plus an aria-label or aria-labelledby. Without them, screen readers do not announce the buttons as a set.
One limit: split button dropdowns do not work inside .btn-group-vertical, according to the Bootstrap docs.
Full-width and centered buttons
| Goal | Classes on the wrapper |
|---|
| Full-width stack | d-grid gap-2 |
| Half-width, centered | d-grid gap-2 col-6 mx-auto |
| Stacked on mobile, inline from md | d-grid gap-2 d-md-block |
| Right-aligned from md | d-grid gap-2 d-md-flex justify-content-md-end |
The last row also wants me-md-2 on the first button so the two do not touch. For a single inline button, text-center on the parent is enough.
The wrapper carries the layout and the buttons stay plain .btn elements. The docs give that as the reason to prefer utilities over button-specific classes: more control over spacing, alignment and responsive behavior.
Split button dropdowns
The .dropdown-toggle-split class trims the caret button’s horizontal padding by 25% and removes its left margin, per the Bootstrap 5.3 dropdown docs.
Put the main action button first and the caret toggle second, both inside .btn-group, and give the caret button hidden text such as “Toggle Dropdown” in .visually-hidden. The menu opens on click and never on hover, which the docs treat as intentional.
Popper positions the menu. Dropdowns inside navbars skip it, because dynamic positioning is not needed there.
Menu direction, alignment and content are covered in this guide to the Bootstrap dropdown component.
How to add icons and loading spinners to a Bootstrap button
Put an icon or spinner element inside the button next to the label, and space it with a margin utility. A loading button also takes the disabled attribute, so a second click can’t fire while the request runs.
Icon buttons
The Bootstrap Icons library ships 2,078 icons at v1.13.2, and Font Awesome says its Free tier holds over 2,000.
Icon count won’t settle the choice, so it comes down to how each one is delivered. Bootstrap Icons comes as SVG files, an SVG sprite or web fonts, and installs with npm i bootstrap-icons. Font Awesome Free uses SVG with JavaScript, or font files and CSS, which makes it a second dependency beside Bootstrap.
A decorative icon takes aria-hidden=”true” so screen readers skip it, the way W3C’s button example hides its SVG. An icon-only button needs an accessible name instead, from aria-label or .visually-hidden text.
Loading spinners
Bootstrap spinners are pure CSS, so showing and hiding them is your JavaScript’s job.
- Add disabled to the button
- Insert a span with spinner-border and spinner-border-sm, marked aria-hidden=”true”
- Put the status text in a span with role=”status”
- Remove the spinner and the disabled attribute when the request finishes
The status text can be visible (“Loading…”) or sit in .visually-hidden, and the docs show both versions.
Spinner animation follows the prefers-reduced-motion setting, so users who ask for less motion get a slowed spinner. A spinner on a button that is not disabled still accepts clicks.
How to customize Bootstrap button colors with CSS variables and Sass
Override –bs-btn-* variables on a custom class for a one-off color, and extend $theme-colors in Sass when a new variant should exist site-wide. Variables work without a build step, while Sass needs one.
Build a custom button class with CSS variables
Bootstrap’s own docs build their branded button this way, with a class that reassigns variables instead of adding new rules.
- Add the class beside .btn, for example class=”btn btn-brand”
- Set –bs-btn-color, –bs-btn-bg and –bs-btn-border-color for the resting state
- Set –bs-btn-hover-color, –bs-btn-hover-bg and –bs-btn-hover-border-color
- Set –bs-btn-active-color, –bs-btn-active-bg and –bs-btn-active-border-color
- Set –bs-btn-focus-shadow-rgb as an RGB triplet for the focus ring
- Set the –bs-btn-disabled-* values so the disabled state matches
Load your stylesheet after Bootstrap’s CSS so the class wins. For selector and load-order problems, see this guide to overriding Bootstrap CSS.
Scope colors with data-bs-theme
Color modes arrived in v5.3.0, according to the Bootstrap docs. Put data-bs-theme on the html element and it applies to the whole page. On a parent it scopes everything inside, and on a single element it scopes just that element.
The docs’ custom “blue” theme shows the button angle. Inside a [data-bs-theme=”blue”] selector it reassigns the background, hover and active variables of .btn-secondary.
Sass: theme colors and the button-variant mixin
One new entry in $theme-colors generates both btn-{name} and btn-outline-{name}, because the button loops run over that map. Alerts and list groups need extra maps for a new color, per the color modes docs, but buttons don’t.
Order matters, since Sass cannot change a variable or map after it has been used. Define custom variables after functions and before variables, and merge your color into $theme-colors after variables and before maps.
Default hover and pressed backgrounds move 15% and 20% from the base color, shaded or tinted depending on text contrast. The $btn-hover-bg-shade-amount and $btn-active-bg-shade-amount variables control them, with matching -tint-amount variables for the tinted side.
Variants built through the mixin pick their text color with color-contrast(). The 5.3.8 release (August 25, 2025) fixed that function for WCAG 2.1 compliance, according to the Bootstrap blog.
Check brand colors in a palette generator built for accessible colors before they go into –bs-btn-bg.
Short version: variables for a single button or component, Sass for a variant the whole site reuses.
What changed in Bootstrap 5 buttons compared to Bootstrap 4
Bootstrap 5 removed btn-block, renamed data-toggle to data-bs-toggle, and dropped jQuery from the plugin code.
| Feature | Bootstrap 4 | Bootstrap 5 |
|---|
| Full-width button | btn-block on the button | d-grid and gap-2 on a wrapper |
| Toggle attribute | data-toggle=”button” | data-bs-toggle=”button” |
| Checkbox or radio toggle | JavaScript and a wrapping element | btn-check on the input, .btn on the label |
| Hidden label text | sr-only | visually-hidden |
| Close button | .close | .btn-close |
Old markup is easy to spot. A snippet with data-toggle, btn-block or sr-only was written for Bootstrap 4 or earlier.
W3Schools’ older Bootstrap tutorial page still lists btn-default and btn-xs. The 5.3 buttons docs list neither.
The docs also disagree with themselves in one spot. The v5.0.0 migration notes say checkbox and radio toggle buttons no longer need JavaScript, while the introduction page still files checkbox and radio functionality under components that require it. A btn-check input is a native checkbox styled by CSS, so the migration notes are the safer reading.
Version numbers are a little messy right now. The 5.3 docs footer and the versions page call v5.3.x the latest release, but the getbootstrap.com homepage now advertises v6.0.0-alpha.1, released on October 8, 2026.
Production code should stay on 5.3.x. The 5.4.0 release that the August 2025 post anticipated does not appear on the versions page.
When Bootstrap buttons go wrong
Buttons that look right and still break usually trace back to one of a few causes. Meaning carried by color alone is one. .btn on elements that aren’t buttons is another, and overrides that fight the framework make a third.
A red btn-danger signals “destructive” to sighted users and nothing to a screen reader. The docs say to put the meaning in visible text or in .visually-hidden text.
A div with .btn gets the look and none of the behavior. W3C’s div-based button example adds role=”button”, tabindex=”0″ and its own keyboard handlers to get there.
Fighting the framework gets expensive. The docs’ own custom button overrides 11 variables for the resting, hover, active and focus states, and leaves the disabled state out. Multiply that across every variant in a design system and the upkeep shows.
Button fixes cover one component. Run the finished page through an accessibility checklist for the whole page before shipping.
Bootstrap button or custom button CSS?
Keep the Bootstrap button if the page already loads Bootstrap CSS and you want variants, sizes, states and toggles without writing them yourself. It also helps if the design can live with theme tokens and a few variable overrides.
Write custom button CSS if buttons are the only Bootstrap component on the page, or if most variants need a full set of overrides.
One custom class is cheap. A whole redesigned button family is cheaper in your own CSS, because you stop paying for framework defaults you override anyway.
Bootstrap Button FAQ
How do I remove the focus outline from a Bootstrap button?
Replace it rather than remove it. The focus ring helper from the 5.3.0 line swaps the default outline for a custom box-shadow ring.
The –bs-btn-focus-box-shadow variable restyles the ring on a single button.
Can a Bootstrap button have rounded or pill-shaped corners?
Yes. Add the .rounded-pill utility for a full pill shape.
For a custom radius, set –bs-btn-border-radius on the button itself.
Why does a Bootstrap button submit my form when I click it?
A button element inside a form defaults to type=”submit” in HTML, and Bootstrap does not change that. Add type=”button” to every button that should not submit.
Bootstrap’s documentation examples mark their non-submit buttons this way.
How do I run code when a Bootstrap button is clicked?
Use plain JavaScript and attach a handler with addEventListener(“click”, handler) on the element.
Bootstrap’s Button class only exposes toggle, getInstance, getOrCreateInstance and dispose, and documents no click events of its own.
Is btn-close the same component as a Bootstrap button?
No. btn-close is a separate close button component, drawn with a background-image SVG instead of a character in the markup.
Its btn-close-white variant was deprecated in 5.3.0 in favor of data-bs-theme=”dark”.
Setting Up Bootstrap Buttons You Can Maintain
Pick the element and variant first, then add states, labels and group roles, and only after that touch variable overrides or Sass. Doing it backwards means restyling markup that later changes.
Customizing carries a cost. The v5.2.0 migration notes say CSS variable coverage stays incomplete until Bootstrap 6, so every –bs-btn-* override written today gets retested at that upgrade.
As of October 28, 2026, v6 is still an alpha. The sequence changes once v6 reaches a stable release with its own migration notes for buttons.
Buttons spend most of their time inside forms, so the Bootstrap form component is the next piece to set up.