Bootstrap Button: 16 Examples of Styles, States and Groups

The .btn class does less than most people expect. Without a modifier such as btn-primary it gives you padding, alignment, a transparent border and background, and no explicit hover or focus styles. Bootstrap 5.3 ships 9 variants, outline styles and two extra sizes.

Below are 16 Bootstrap button examples you can copy, followed by a guide to variants, states, groups and customization.

By Bogdan Sandu ยท Updated October 29, 2026

Every example runs on Bootstrap 5.3.8 from the jsDelivr CDN. Add these tags to your page, then paste an example's HTML, CSS and JS.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.2/font/bootstrap-icons.min.css" rel="stylesheet" integrity="sha384-nWN3KqaaVcbEsCjLSV+2jMjh62Iny/+mN3srM8TZeM8N6aj7eDl5wwYggjAJ4/sJ" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>

Themed buttons with CSS variables

Bootstrap 5.3 buttons read their colors from local --bs-btn-* variables, so a new look is a class that reassigns them. These demos go past the default palette without touching Sass.

Custom brand button, every state

  • --bs-btn-* variables
  • btn-outline-*
  • disabled

Hover, press or Tab to the buttons to see each state.

A deep teal brand button made only by reassigning the --bs-btn-* variables for the rest, hover, active, focus and disabled states, the same pattern the Bootstrap docs use for their own branded button. The outline twin reuses the same tokens, and the focus ring color comes from --bs-btn-focus-shadow-rgb.

View code
<div class="card border-0 shadow-sm bbt-invoice">
  <div class="card-body p-4">
    <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
      <div>
        <h3 class="h5 mb-1">Invoice INV-2047</h3>
        <p class="text-body-secondary small mb-0">Harbor &amp; Pine Studio, due Nov 14</p>
      </div>
      <span class="badge rounded-pill text-bg-light border">Draft</span>
    </div>
    <p class="fs-3 fw-semibold mb-4">$3,280.00</p>
    <div class="d-flex flex-wrap gap-2">
      <button type="button" class="btn bbt-brand"><i class="bi bi-send me-2" aria-hidden="true"></i>Send invoice</button>
      <button type="button" class="btn bbt-brand-outline">Preview</button>
      <button type="button" class="btn bbt-brand" disabled>Mark as paid</button>
    </div>
  </div>
</div>

Soft buttons from subtle colors

  • *-bg-subtle
  • *-text-emphasis
  • *-border-subtle

Soft buttons for a review queue, built from the 5.3 theme tokens --bs-*-bg-subtle, --bs-*-border-subtle and --bs-*-text-emphasis. One prefixed class maps them onto --bs-btn-* and each color class only swaps three tokens, so the buttons also adapt in dark mode.

View code
<div class="card border-0 shadow-sm bbt-review">
  <div class="card-body p-4">
    <div class="d-flex align-items-center gap-3 mb-3">
      <span class="bbt-avatar rounded-circle bg-primary-subtle text-primary-emphasis fw-semibold" aria-hidden="true">MR</span>
      <div>
        <h3 class="h6 mb-0">Pull request #418</h3>
        <p class="small text-body-secondary mb-0">Mara Ruiz wants to merge 6 commits into main</p>
      </div>
    </div>
    <div class="d-flex flex-wrap gap-2">
      <button type="button" class="btn btn-sm bbt-soft bbt-soft-success"><i class="bi bi-check2 me-1" aria-hidden="true"></i>Approve</button>
      <button type="button" class="btn btn-sm bbt-soft bbt-soft-warning"><i class="bi bi-pencil me-1" aria-hidden="true"></i>Request changes</button>
      <button type="button" class="btn btn-sm bbt-soft bbt-soft-primary"><i class="bi bi-person-plus me-1" aria-hidden="true"></i>Assign</button>
      <button type="button" class="btn btn-sm bbt-soft bbt-soft-danger"><i class="bi bi-x-lg me-1" aria-hidden="true"></i>Close</button>
    </div>
  </div>
</div>

Dark mode deploy console

  • data-bs-theme="dark"
  • btn-outline-*
  • btn-link

The card carries data-bs-theme="dark", so the outline, link and secondary buttons pick up the dark color mode tokens even on a light page. A custom lime button reassigns --bs-btn-* for the one action that matters, with dark text for contrast.

View code
<div class="card bbt-console" data-bs-theme="dark">
  <div class="card-body p-4">
    <div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
      <h3 class="h6 mb-0 font-monospace">orbit-api / build 1.42.0</h3>
      <span class="badge text-bg-success"><i class="bi bi-check-circle me-1" aria-hidden="true"></i>Checks passed</span>
    </div>
    <p class="small text-body-secondary mb-4">Staging has been healthy for 38 minutes. Error rate 0.02%, p95 latency 184 ms.</p>
    <div class="d-flex flex-wrap gap-2 align-items-center">
      <button type="button" class="btn bbt-lime"><i class="bi bi-rocket-takeoff me-2" aria-hidden="true"></i>Promote to production</button>
      <button type="button" class="btn btn-outline-secondary"><i class="bi bi-arrow-counterclockwise me-2" aria-hidden="true"></i>Roll back</button>
      <button type="button" class="btn btn-link">View logs</button>
    </div>
  </div>
</div>

Hero CTA pair

  • btn-lg
  • --bs-btn-border-radius
  • d-grid
  • d-sm-flex

One strong call to action and one quiet alternative, both btn-lg. The wrapper is d-grid on phones and d-sm-flex from 576px, so the buttons stack full width on small screens and sit side by side above that. The ink and outline styles are --bs-btn-* overrides with a custom radius.

View code
<section class="bbt-hero px-4 py-5">
  <div class="bbt-hero-inner mx-auto text-center">
    <h2 class="display-6 fw-bold mb-3">Field notes that sort themselves</h2>
    <p class="lead bbt-hero-muted mb-4">Fieldnote tags every photo, sketch and voice memo by place and date, so your research is ready when you get back to the desk.</p>
    <div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
      <a href="#" class="btn btn-lg bbt-ink">Start free trial<i class="bi bi-arrow-right ms-2" aria-hidden="true"></i></a>
      <a href="#" class="btn btn-lg bbt-ink-outline"><i class="bi bi-play-circle me-2" aria-hidden="true"></i>Watch the tour</a>
    </div>
    <p class="small bbt-hero-muted mt-3 mb-0">14 days free. No card required.</p>
  </div>
</section>

Button states and feedback

Loading, pressed, confirm and disabled states built from Bootstrap classes, ARIA attributes and a few lines of plain JavaScript.

Loading button with spinner

  • spinner-border-sm
  • disabled
  • aria-busy
  • role="status"

Click the buttons to see the state change.

On click the script disables the button, sets aria-busy="true" and swaps the label for a spinner-border-sm plus role="status" text, then shows a saved state and resets. The disabled attribute stops double submits while the fake request runs.

View code
<div class="card border-0 shadow-sm bbt-profile">
  <div class="card-body p-4">
    <h3 class="h6 mb-3">Notification email</h3>
    <label for="bbt-email" class="form-label small text-body-secondary">Send alerts to</label>
    <input type="email" class="form-control mb-3" id="bbt-email" value="alerts@northwind.example">
    <div class="d-flex justify-content-end gap-2">
      <button type="button" class="btn btn-outline-secondary">Cancel</button>
      <button type="button" class="btn btn-primary bbt-save" id="bbt-save">
        <span class="bbt-save-idle">Save changes</span>
        <span class="bbt-save-busy d-none"><span class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span><span role="status">Saving...</span></span>
        <span class="bbt-save-done d-none"><i class="bi bi-check2 me-1" aria-hidden="true"></i>Saved</span>
      </button>
    </div>
  </div>
</div>

Text format toggle toolbar

  • data-bs-toggle="button"
  • aria-pressed
  • btn-toolbar
  • btn-group

Click the buttons to see the state change.

Bold, italic and underline are plugin toggle buttons: data-bs-toggle="button" flips .active and aria-pressed on every click while the aria-label stays fixed. The groups sit in a btn-toolbar with role="toolbar", and a small script mirrors the pressed state onto the sample text.

View code
<div class="card border-0 shadow-sm bbt-editor">
  <div class="card-header bg-body-tertiary">
    <div class="btn-toolbar gap-2" role="toolbar" aria-label="Text formatting">
      <div class="btn-group btn-group-sm" role="group" aria-label="Text style">
        <button type="button" class="btn btn-outline-secondary bbt-fmt" data-bbt-fmt="fw-bold" data-bs-toggle="button" aria-pressed="false" aria-label="Bold"><i class="bi bi-type-bold" aria-hidden="true"></i></button>
        <button type="button" class="btn btn-outline-secondary bbt-fmt" data-bbt-fmt="fst-italic" data-bs-toggle="button" aria-pressed="false" aria-label="Italic"><i class="bi bi-type-italic" aria-hidden="true"></i></button>
        <button type="button" class="btn btn-outline-secondary bbt-fmt" data-bbt-fmt="text-decoration-underline" data-bs-toggle="button" aria-pressed="false" aria-label="Underline"><i class="bi bi-type-underline" aria-hidden="true"></i></button>
      </div>
      <div class="btn-group btn-group-sm" role="group" aria-label="Insert">
        <button type="button" class="btn btn-outline-secondary" aria-label="Insert link"><i class="bi bi-link-45deg" aria-hidden="true"></i></button>
        <button type="button" class="btn btn-outline-secondary" aria-label="Insert bulleted list"><i class="bi bi-list-ul" aria-hidden="true"></i></button>
      </div>
    </div>
  </div>
  <div class="card-body">
    <p class="mb-0 bbt-sample" id="bbt-sample">The ferry leaves at 7:40, so pack the night before and keep the tickets on your phone.</p>
  </div>
</div>

Two-step delete button

  • btn-outline-danger
  • btn-danger
  • btn-link
  • aria-live

Click the buttons to see the state change.

The first click swaps btn-outline-danger for a solid btn-danger that asks for confirmation and reveals a btn-link cancel. The second click deletes, a polite live region announces each step, and the button reverts on its own after five seconds.

View code
<div class="card border-danger-subtle bbt-danger-zone">
  <div class="card-body p-4">
    <h3 class="h6 text-danger-emphasis mb-1">Delete project</h3>
    <p class="small text-body-secondary mb-3">Removes "Atlas Redesign", its 214 files and every share link. This cannot be undone.</p>
    <div class="d-flex flex-wrap align-items-center gap-2">
      <button type="button" class="btn btn-outline-danger bbt-del" id="bbt-del"><i class="bi bi-trash3 me-2" aria-hidden="true"></i><span class="bbt-del-label">Delete project</span></button>
      <button type="button" class="btn btn-link link-secondary d-none" id="bbt-del-cancel">Cancel</button>
    </div>
    <p class="small mt-3 mb-0 text-body-secondary" id="bbt-del-status" aria-live="polite"></p>
  </div>
</div>

Disabled button and disabled link

  • disabled
  • .disabled
  • aria-disabled
  • tooltip

Hover or focus the disabled export button.

The button uses the disabled attribute. The anchor has no disabled attribute, so it gets .disabled, role="button", aria-disabled="true" and no href. Disabled elements cannot trigger a tooltip, so the explanation sits on a focusable wrapper span, as the Bootstrap tooltip docs recommend.

View code
<div class="card border-0 shadow-sm bbt-export">
  <div class="card-body p-4">
    <h3 class="h6 mb-1">Q3 sales report</h3>
    <p class="small text-body-secondary mb-3">Generated Oct 2, 1,284 rows</p>
    <div class="d-flex flex-wrap gap-2">
      <button type="button" class="btn btn-primary"><i class="bi bi-filetype-csv me-2" aria-hidden="true"></i>Export CSV</button>
      <span class="d-inline-block bbt-tip-wrap" tabindex="0" data-bs-toggle="tooltip" data-bs-title="PDF export is on the Team plan">
        <button type="button" class="btn btn-outline-primary" disabled><i class="bi bi-filetype-pdf me-2" aria-hidden="true"></i>Export PDF</button>
      </span>
      <a class="btn btn-outline-secondary disabled" role="button" aria-disabled="true">View archive</a>
    </div>
  </div>
</div>

Groups, checks and dropdowns

Button groups, btn-check inputs and split dropdowns turn plain buttons into segmented controls, filter chips and plan pickers.

Segmented control from radio buttons

  • btn-check
  • radio
  • --bs-btn-active-*

Pick an option or open the menu.

Three btn-check radios with label buttons make a calendar view switcher. A checked btn-check paints its label with the --bs-btn-active-* variables, so the white raised segment is just three variable overrides plus a shadow. The radios stay native, so arrow keys move between options.

View code
<div class="bbt-cal card border-0 shadow-sm">
  <div class="card-body p-3 d-flex flex-wrap align-items-center justify-content-between gap-3">
    <h3 class="h6 mb-0">November 2026</h3>
    <div class="bbt-seg d-inline-flex p-1 rounded-3" role="group" aria-label="Calendar view">
      <input type="radio" class="btn-check" name="bbt-view" id="bbt-view-day" autocomplete="off">
      <label class="btn btn-sm" for="bbt-view-day">Day</label>
      <input type="radio" class="btn-check" name="bbt-view" id="bbt-view-week" autocomplete="off" checked>
      <label class="btn btn-sm" for="bbt-view-week">Week</label>
      <input type="radio" class="btn-check" name="bbt-view" id="bbt-view-month" autocomplete="off">
      <label class="btn btn-sm" for="bbt-view-month">Month</label>
    </div>
  </div>
</div>

Plan picker cards

  • btn-check
  • btn-outline-primary
  • d-grid
  • text-start

Pick an option or open the menu.

Each plan is a radio input with btn-check and a full-width label styled as btn-outline-primary text-start. The checked card swaps to a subtle background and emphasis text through the --bs-btn-active-* variables, and the continue button reads the chosen plan.

View code
<div class="card border-0 shadow-sm bbt-plans">
  <div class="card-body p-4">
    <h3 class="h5 mb-3" id="bbt-plans-title">Choose a plan</h3>
    <div class="d-grid gap-2 mb-3" role="radiogroup" aria-labelledby="bbt-plans-title">
      <input type="radio" class="btn-check" name="bbt-plan" id="bbt-plan-solo" value="Solo" autocomplete="off">
      <label class="btn btn-outline-primary text-start bbt-plan" for="bbt-plan-solo">
        <span class="d-flex justify-content-between"><span class="fw-semibold">Solo</span><span>$9/mo</span></span>
        <span class="d-block small bbt-plan-sub">1 seat, 20 projects</span>
      </label>
      <input type="radio" class="btn-check" name="bbt-plan" id="bbt-plan-team" value="Team" autocomplete="off" checked>
      <label class="btn btn-outline-primary text-start bbt-plan" for="bbt-plan-team">
        <span class="d-flex justify-content-between"><span class="fw-semibold">Team</span><span>$29/mo</span></span>
        <span class="d-block small bbt-plan-sub">Up to 10 seats, unlimited projects</span>
      </label>
      <input type="radio" class="btn-check" name="bbt-plan" id="bbt-plan-studio" value="Studio" autocomplete="off">
      <label class="btn btn-outline-primary text-start bbt-plan" for="bbt-plan-studio">
        <span class="d-flex justify-content-between"><span class="fw-semibold">Studio</span><span>$79/mo</span></span>
        <span class="d-block small bbt-plan-sub">Unlimited seats, SSO, audit log</span>
      </label>
    </div>
    <div class="d-grid">
      <button type="button" class="btn btn-primary btn-lg" id="bbt-plan-go">Continue with Team</button>
    </div>
  </div>
</div>

Checkbox filter chips

  • btn-check
  • checkbox
  • rounded-pill
  • btn-sm

Pick an option or open the menu.

Checkbox btn-check inputs styled as rounded-pill chips. A check icon inside each label only shows when its input is checked, so state never depends on color alone, and the apply button counts the active filters.

View code
<div class="card border-0 shadow-sm bbt-filters">
  <div class="card-body p-4">
    <h3 class="h6 mb-3" id="bbt-diet-title">Dietary needs</h3>
    <div class="d-flex flex-wrap gap-2 mb-4" role="group" aria-labelledby="bbt-diet-title">
      <input type="checkbox" class="btn-check bbt-chip-input" id="bbt-diet-veg" autocomplete="off" checked>
      <label class="btn btn-sm btn-outline-success rounded-pill bbt-chip" for="bbt-diet-veg"><i class="bi bi-check-lg bbt-tick" aria-hidden="true"></i>Vegetarian</label>
      <input type="checkbox" class="btn-check bbt-chip-input" id="bbt-diet-vegan" autocomplete="off">
      <label class="btn btn-sm btn-outline-success rounded-pill bbt-chip" for="bbt-diet-vegan"><i class="bi bi-check-lg bbt-tick" aria-hidden="true"></i>Vegan</label>
      <input type="checkbox" class="btn-check bbt-chip-input" id="bbt-diet-gf" autocomplete="off" checked>
      <label class="btn btn-sm btn-outline-success rounded-pill bbt-chip" for="bbt-diet-gf"><i class="bi bi-check-lg bbt-tick" aria-hidden="true"></i>Gluten free</label>
      <input type="checkbox" class="btn-check bbt-chip-input" id="bbt-diet-nut" autocomplete="off">
      <label class="btn btn-sm btn-outline-success rounded-pill bbt-chip" for="bbt-diet-nut"><i class="bi bi-check-lg bbt-tick" aria-hidden="true"></i>Nut free</label>
      <input type="checkbox" class="btn-check bbt-chip-input" id="bbt-diet-df" autocomplete="off">
      <label class="btn btn-sm btn-outline-success rounded-pill bbt-chip" for="bbt-diet-df"><i class="bi bi-check-lg bbt-tick" aria-hidden="true"></i>Dairy free</label>
    </div>
    <div class="d-flex justify-content-between align-items-center gap-2">
      <button type="button" class="btn btn-link px-0 link-secondary" id="bbt-diet-clear">Clear</button>
      <button type="button" class="btn btn-success" id="bbt-diet-apply">Apply 2 filters</button>
    </div>
  </div>
</div>

Split button dropdown

  • btn-group
  • dropdown-toggle-split
  • dropdown-menu-end
  • visually-hidden

Pick an option or open the menu.

A publish bar from a blog editor. The main action and the dropdown-toggle-split caret share a btn-group, the caret carries visually-hidden text for screen readers, and dropdown-menu-end keeps the menu inside narrow screens.

View code
<div class="card border-0 shadow-sm bbt-publish">
  <div class="card-body p-3 d-flex flex-wrap align-items-center justify-content-between gap-3">
    <div class="small text-body-secondary"><i class="bi bi-cloud-check me-1" aria-hidden="true"></i>Draft saved 2 min ago</div>
    <div class="d-flex gap-2">
      <button type="button" class="btn btn-outline-secondary">Preview</button>
      <div class="btn-group">
        <button type="button" class="btn btn-primary">Publish</button>
        <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split bbt-split" data-bs-toggle="dropdown" aria-expanded="false">
          <span class="visually-hidden">More publish options</span>
        </button>
        <ul class="dropdown-menu dropdown-menu-end shadow-sm">
          <li><a class="dropdown-item" href="#"><i class="bi bi-calendar-event me-2" aria-hidden="true"></i>Schedule for later</a></li>
          <li><a class="dropdown-item" href="#"><i class="bi bi-people me-2" aria-hidden="true"></i>Publish to members only</a></li>
          <li><hr class="dropdown-divider"></li>
          <li><a class="dropdown-item" href="#"><i class="bi bi-file-earmark me-2" aria-hidden="true"></i>Save as template</a></li>
        </ul>
      </div>
    </div>
  </div>
</div>

Icons, layout and real-world sets

Icon buttons with accessible names, full-width d-grid stacks, share links and close buttons, laid out with utilities instead of button-specific classes.

Pill and square icon buttons

  • Bootstrap Icons
  • aria-label
  • rounded-pill
  • btn-light

Square icon-only buttons get an aria-label for their name and aria-hidden on the icon, sized to 44px with --bs-btn-padding-* so they stay easy to tap. The pill buttons pair icon and text, and rounded-pill does the shape.

View code
<figure class="card border-0 shadow-sm overflow-hidden bbt-photo mb-0">
  <img src="fjord.webp" class="card-img-top" alt="Deep blue fjord between steep grey cliffs, seen from a rocky ledge high above" width="800" height="600">
  <figcaption class="card-body">
    <div class="d-flex justify-content-between align-items-start gap-2 mb-3">
      <div>
        <h3 class="h6 mb-0">Above the fjord</h3>
        <p class="small text-body-secondary mb-0">Ines Halvorsen, 6000 x 4000</p>
      </div>
      <div class="d-flex gap-1">
        <button type="button" class="btn btn-light bbt-icon" aria-label="Like"><i class="bi bi-heart" aria-hidden="true"></i></button>
        <button type="button" class="btn btn-light bbt-icon" aria-label="Save to collection"><i class="bi bi-bookmark" aria-hidden="true"></i></button>
        <button type="button" class="btn btn-light bbt-icon" aria-label="More options"><i class="bi bi-three-dots" aria-hidden="true"></i></button>
      </div>
    </div>
    <div class="d-flex flex-wrap gap-2">
      <button type="button" class="btn btn-dark rounded-pill bbt-pill"><i class="bi bi-download me-2" aria-hidden="true"></i>Download</button>
      <button type="button" class="btn btn-outline-dark rounded-pill bbt-pill"><i class="bi bi-plus-lg me-2" aria-hidden="true"></i>Add to board</button>
    </div>
  </figcaption>
</figure>

Full-width checkout actions

  • d-grid
  • gap-2
  • btn-lg
  • btn-link

The Bootstrap 5 replacement for btn-block: a d-grid gap-2 wrapper stretches every button to the card width. The pay button is btn-lg with a lock icon, the alternative is an outline button, and the way back is a quiet btn-link.

View code
<div class="card border-0 shadow-sm bbt-checkout">
  <div class="card-body p-4">
    <h3 class="h5 mb-3">Order summary</h3>
    <div class="d-flex gap-3 align-items-center mb-3">
      <img src="canal-houses.webp" class="rounded-2 bbt-thumb" alt="Canal lined with old brick houses and small moored boats on a sunny day" width="800" height="600">
      <div class="flex-grow-1">
        <p class="mb-0 fw-semibold">Canal Morning print</p>
        <p class="small text-body-secondary mb-0">A3, matte, unframed</p>
      </div>
      <span class="fw-semibold">$42.00</span>
    </div>
    <dl class="row small mb-3">
      <dt class="col-6 fw-normal text-body-secondary">Shipping</dt><dd class="col-6 text-end mb-1">$6.00</dd>
      <dt class="col-6">Total</dt><dd class="col-6 text-end fw-semibold mb-0">$48.00</dd>
    </dl>
    <div class="d-grid gap-2">
      <button type="button" class="btn btn-dark btn-lg"><i class="bi bi-lock-fill me-2" aria-hidden="true"></i>Pay $48.00</button>
      <button type="button" class="btn btn-outline-secondary">Pay in 3 installments</button>
      <a href="#" class="btn btn-link link-secondary">Return to cart</a>
    </div>
  </div>
</div>

Share link buttons

  • a.btn
  • Bootstrap Icons
  • btn-outline-secondary
  • --bs-btn-hover-*

Share targets change the URL, so they are anchors with .btn rather than buttons. Generic Bootstrap Icons stand in for channels, each link gets its own hover color through --bs-btn-hover-*, and the icon-only versions on phones keep an aria-label.

View code
<div class="card border-0 shadow-sm bbt-share">
  <div class="card-body p-4">
    <h3 class="h6 mb-1">Share this recipe</h3>
    <p class="small text-body-secondary mb-3">Smoked paprika lentil soup, 35 minutes</p>
    <div class="d-flex flex-wrap gap-2">
      <a href="#" class="btn btn-outline-secondary bbt-share-btn bbt-share-mail" aria-label="Share by email"><i class="bi bi-envelope" aria-hidden="true"></i><span class="d-none d-sm-inline ms-2">Email</span></a>
      <a href="#" class="btn btn-outline-secondary bbt-share-btn bbt-share-chat" aria-label="Share in a message"><i class="bi bi-chat-dots" aria-hidden="true"></i><span class="d-none d-sm-inline ms-2">Message</span></a>
      <a href="#" class="btn btn-outline-secondary bbt-share-btn bbt-share-post" aria-label="Share as a post"><i class="bi bi-megaphone" aria-hidden="true"></i><span class="d-none d-sm-inline ms-2">Post</span></a>
      <a href="#" class="btn btn-outline-secondary bbt-share-btn bbt-share-print" aria-label="Print recipe"><i class="bi bi-printer" aria-hidden="true"></i><span class="d-none d-sm-inline ms-2">Print</span></a>
    </div>
  </div>
</div>

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.

ElementDisable withAttributes to addBest use
buttondisabled attributetype=”button” or type=”submit”Actions, form submit
a.disabled classrole=”button”, aria-disabledNavigation
inputdisabled attributetype and valueFixed 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

GoalClasses on the wrapper
Full-width stackd-grid gap-2
Half-width, centeredd-grid gap-2 col-6 mx-auto
Stacked on mobile, inline from mdd-grid gap-2 d-md-block
Right-aligned from mdd-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.

  1. Add disabled to the button
  2. Insert a span with spinner-border and spinner-border-sm, marked aria-hidden=”true”
  3. Put the status text in a span with role=”status”
  4. 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.

  1. Add the class beside .btn, for example class=”btn btn-brand”
  2. Set –bs-btn-color, –bs-btn-bg and –bs-btn-border-color for the resting state
  3. Set –bs-btn-hover-color, –bs-btn-hover-bg and –bs-btn-hover-border-color
  4. Set –bs-btn-active-color, –bs-btn-active-bg and –bs-btn-active-border-color
  5. Set –bs-btn-focus-shadow-rgb as an RGB triplet for the focus ring
  6. 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.

FeatureBootstrap 4Bootstrap 5
Full-width buttonbtn-block on the buttond-grid and gap-2 on a wrapper
Toggle attributedata-toggle=”button”data-bs-toggle=”button”
Checkbox or radio toggleJavaScript and a wrapping elementbtn-check on the input, .btn on the label
Hidden label textsr-onlyvisually-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.