Getting Started
Including Bootstrap
Include Bootstrap via CDN for quick start — the CSS goes in the <head>, the JS bundle (which includes Popper) goes before </body>. The bundle.min.js includes Popper for tooltips/dropdowns/popovers. With npm, import the CSS and JS in your entry file. Bootstrap 5 dropped jQuery dependency — all components work with vanilla JS.
<!-- CSS only (in <head>) -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- JS bundle (before </body>) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<!-- or install via npm -->
<!-- npm install [email protected] -->
<!-- import in JS -->
// import 'bootstrap/dist/css/bootstrap.min.css'
// import 'bootstrap' // imports all JSContainers
Containers are the most basic layout element — they center content and add horizontal padding. .container is fixed-width at each breakpoint (max-width changes); .container-fluid is always 100% wide. Responsive containers (container-sm/md/lg/xl/xxl) are 100% wide until the named breakpoint, then become fixed-width. Use .container for most layouts; .container-fluid for full-width apps.
<!-- fixed-width container (responsive breakpoints) -->
<div class="container">
<!-- content centered with max-width -->
</div>
<!-- full-width container -->
<div class="container-fluid">
<!-- spans entire viewport width -->
</div>
<!-- responsive containers (max-width at breakpoint) -->
<div class="container-sm"> <!-- 100% until sm, then fixed -->
<div class="container-md">
<div class="container-xl">
<!-- 100% wide until the breakpoint, then max-width applied -->
<div class="container-xxl">Responsive Breakpoints
Bootstrap uses a mobile-first, min-width media query system. There are 6 breakpoints: xs (default), sm (576px), md (768px), lg (992px), xl (1200px), xxl (1400px). Classes like col-md-6 apply at md AND above. To show/hide at breakpoints, use d-none (display:none) and d-{breakpoint}-block. This mobile-first approach means you style for small screens first, then add complexity for larger ones.
/* Bootstrap 5 breakpoints (min-width, mobile-first) */
/* xs: <576px (default, no media query needed) */
/* sm: >=576px */
/* md: >=768px */
/* lg: >=992px */
/* xl: >=1200px */
/* xxl: >=1400px */
<!-- classes apply AT and ABOVE the breakpoint -->
<div class="col-12 col-md-6 col-lg-4">
<!-- full width on mobile, half on md, third on lg -->
</div>
<!-- hidden/visible at breakpoints -->
<div class="d-none d-md-block"> <!-- hidden below md -->
<div class="d-block d-md-none"> <!-- visible below md only -->Reboot & Base Styles
Reboot is Bootstrap's CSS reset — it normalizes browser styles using rem units (root em, based on the html font-size of 16px). All elements get box-sizing: border-box. Headings, paragraphs, lists, tables, and links get sensible defaults. Reboot removes top margins from headings and lists, setting them to 0. This gives a consistent baseline across browsers before Bootstrap's components layer on top.
<!-- Reboot normalizes styles: box-sizing, margins, etc. -->
<!-- body gets: font-family, font-size (1rem), line-height (1.5), color -->
<!-- headings use rem units, margin-top removed -->
<h1>Heading 1 (2.5rem)</h1>
<h2>Heading 2 (2rem)</h2>
<!-- links get color and underline on hover -->
<a href="#">Default link</a>
<!-- tables get basic styling -->
<table class="table">...</table>
<!-- <ul>/<ol> have padding-left removed -->
<ul>
<li>Item</li>
</ul>Customizing with CSS Variables
Bootstrap 5 exposes hundreds of CSS custom properties (variables) prefixed with --bs-. Override them in :root to theme your site globally. Dark mode is built-in via data-bs-theme='dark' on the <html> element, which swaps a set of variables. This makes theming much easier than the old Sass-variable approach — you can change colors, fonts, spacing, and radii with plain CSS, no build step needed.
:root {
/* override Bootstrap's CSS variables */
--bs-primary: #0d6efd;
--bs-body-bg: #ffffff;
--bs-body-color: #212529;
--bs-font-sans-serif: 'Inter', system-ui, sans-serif;
--bs-border-radius: 0.375rem;
}
/* dark mode via data attribute */
[data-bs-theme="dark"] {
--bs-body-bg: #212529;
--bs-body-color: #f8f9fa;
}
<!-- use in HTML -->
<div style="background: var(--bs-primary); color: var(--bs-white);">
Custom styled
</div>Layout Basics
Rows & Columns
.row is a flex container (display: flex) with negative horizontal margins to offset column padding. Direct .col children flex equally to fill the row. Specify col-{1-12} for fixed proportions (out of 12). Without a number, .col takes equal remaining space. The 12-column grid is the core of Bootstrap layouts — combine col-{n} and auto .col for flexible layouts.
<!-- .row creates a flex container with negative margins -->
<!-- .col children flex equally -->
<div class="container">
<div class="row">
<div class="col">Column 1</div>
<div class="col">Column 2</div>
<div class="col">Column 3</div>
</div>
<!-- 3 equal columns -->
</div>
<!-- sized columns -->
<div class="row">
<div class="col-8">8/12 width</div>
<div class="col-4">4/12 width</div>
</div>
<!-- auto + sized -->
<div class="row">
<div class="col">auto width (content-based)</div>
<div class="col-6">exactly half</div>
<div class="col">auto width</div>
</div>Gutters (Spacing Between Columns)
Gutters are the padding between columns, controlled by the g-* classes (g-0 to g-5). g-0 removes all gutters (edge-to-edge columns). g-3 is the default. Gutters work both horizontally (between columns) and vertically (between rows when columns wrap). Use responsive variants (g-2 g-md-4) for different spacing at breakpoints. The gutter system replaced the old gutter-only horizontal padding approach.
<!-- default gutters (1.5rem padding on each side of columns) -->
<div class="row">
<div class="col-6">A</div>
<div class="col-6">B</div>
</div>
<!-- remove gutters -->
<div class="row g-0">
<div class="col-6">A</div>
<div class="col-6">B</div>
</div>
<!-- custom gutter sizes: g-0 to g-5 -->
<div class="row g-3">
<div class="col-6">A</div>
<div class="col-6">B</div>
</div>
<!-- responsive gutters -->
<div class="row g-2 g-md-4">
<div class="col-6">A</div>
<div class="col-6">B</div>
</div>
<!-- vertical gutters (row spacing) -->
<div class="row g-3">
<div class="col-12">Row 1</div>
<div class="col-12">Row 2 (g-3 gap above)</div>
</div>Column Wrapping & Alignment
Columns wrap to a new line when their total exceeds 12. Control vertical alignment with align-items-* on the row (start/center/end). Control horizontal distribution with justify-content-* (start/center/end/between/around/evenly). These use flexbox under the hood. Wrapping is automatic — no need for explicit row breaks. This makes masonry-like layouts easy with just col-* classes.
<!-- columns wrap when they exceed 12 total -->
<div class="row">
<div class="col-9">9 cols</div>
<div class="col-4">4 cols → wraps to next line (9+4=13)</div>
<div class="col-6">6 cols (new line)</div>
</div>
<!-- vertical alignment -->
<div class="row align-items-center">
<div class="col">Vertically centered</div>
</div>
<!-- options: align-items-start, -center, -end -->
<div class="row align-items-end">...</div>
<!-- horizontal alignment -->
<div class="row justify-content-center">
<div class="col-6">Centered horizontally</div>
</div>
<!-- options: justify-content-start, -center, -end, -between, -around, -evenly -->Column Ordering
Order classes reorder columns visually without changing HTML. order-first, order-{1-5}, order-last provide 7 levels. Use responsive variants (order-md-first) to change order at breakpoints — useful for SEO (important content first in HTML) while showing it differently on mobile. Offsets (offset-md-4) add left margin to push columns right — useful for centering or indenting. Both rely on flexbox order and margin utilities.
<!-- order classes: order-first, order-{1-5}, order-last -->
<div class="row">
<div class="col order-2">First in HTML, second visually</div>
<div class="col order-1">Second in HTML, first visually</div>
<div class="col order-3">Third in both</div>
</div>
<!-- responsive ordering -->
<div class="row">
<div class="col order-last order-md-first">
Last on mobile, first on desktop
</div>
<div class="col">Normal</div>
</div>
<!-- column offset (push right) -->
<div class="row">
<div class="col-md-4 offset-md-4">Centered (offset 4)</div>
</div>
<!-- offset responsive -->
<div class="col-md-4 offset-md-2 offset-lg-4">...</div>Z-Index & Stacking
Bootstrap uses a z-index scale for stacking: z-1, z-2, z-3 (and negative z-n0, z-n1, z-n2). Components like modals, dropdowns, and tooltips have higher built-in z-index values (1000-1080). For z-index to work, the element needs position (relative, absolute, fixed, or sticky). Avoid arbitrary z-index values — use Bootstrap's scale for consistency. The stacking order is designed so modals always appear above dropdowns, which appear above navbars.
/* Bootstrap's z-index scale */
/* z-1 to z-3, plus z-n0, z-n1, z-n2 for negatives */
<div class="z-3 position-relative">High z-index</div>
<div class="z-1 position-relative">Low z-index</div>
<!-- components have built-in z-index: -->
/* dropdown: 1000 */
/* sticky: 1020 */
/* fixed: 1030 */
/* modal-backdrop: 1050 */
/* modal: 1055 */
/* popover: 1070 */
/* tooltip: 1080 */
<!-- use position utilities for stacking context -->
<div class="position-relative z-3">
Needs position for z-index to apply
</div>Grid System
Basic Grid
Bootstrap's grid is a 12-column system. col-{1-12} specifies how many of the 12 columns an element spans. col-12 = full width, col-6 = half, col-4 = third, col-3 = quarter. Columns within a row should sum to 12 (or less — remaining space is distributed). Without a number, .col divides remaining space equally. The grid uses flexbox — rows are flex containers, columns are flex items.
<!-- 12-column grid -->
<div class="container">
<div class="row">
<div class="col-12">Full width (12/12)</div>
</div>
<div class="row">
<div class="col-6">Half (6/12)</div>
<div class="col-6">Half (6/12)</div>
</div>
<div class="row">
<div class="col-4">Third (4/12)</div>
<div class="col-4">Third (4/12)</div>
<div class="col-4">Third (4/12)</div>
</div>
<div class="row">
<div class="col-3">Quarter (3/12)</div>
<div class="col-9">Three-quarters (9/12)</div>
</div>
</div>Responsive Columns
Responsive column classes (col-sm-*, col-md-*, col-lg-*, col-xl-*, col-xxl-*) apply at that breakpoint AND above (mobile-first). The smallest applicable class wins. col-12 col-md-6 col-lg-4 means: full-width on mobile, half on tablets, third on desktop. row-cols-* sets the number of equal-width columns per row at a breakpoint — simpler than specifying col-* on each child when all columns are equal.
<!-- different widths at different breakpoints -->
<div class="row">
<!-- full on mobile, half on md, third on lg -->
<div class="col-12 col-md-6 col-lg-4">Card 1</div>
<div class="col-12 col-md-6 col-lg-4">Card 2</div>
<div class="col-12 col-md-6 col-lg-4">Card 3</div>
</div>
<!-- mobile-first: base class applies to all, -->
<!-- larger breakpoints override -->
<div class="col-12 col-sm-6 col-md-4 col-lg-3 col-xl-2">
1 per row on xs, 2 on sm, 3 on md, 4 on lg, 6 on xl
</div>
<!-- equal-width columns at breakpoint -->
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3">
<div class="col">Auto-width card</div>
<div class="col">Auto-width card</div>
<div class="col">Auto-width card</div>
</div>Nesting Columns
To nest grids, put a .row inside a .col — the nested row creates a new 12-column context within the parent column's width. The nested columns (col-4, col-8) divide the parent's width, not the full page. Nesting is essential for complex layouts like sidebars with sub-sections. Keep nesting shallow (2-3 levels max) to avoid overly narrow columns and maintain readability.
<!-- nest a row inside a column -->
<div class="row">
<div class="col-6">
<!-- outer column -->
<h3>Left half</h3>
<!-- nested row: new 12-column context -->
<div class="row">
<div class="col-4">Nested 1/3 of parent</div>
<div class="col-8">Nested 2/3 of parent</div>
</div>
</div>
<div class="col-6">
<h3>Right half</h3>
</div>
</div>
<!-- the nested row creates a new 12-col grid -->
<!-- within the parent column's width -->Column Offsets & Margins
offset-{breakpoint}-{n} adds n columns of left margin, useful for centering or indentation. offset-md-4 on a col-md-4 centers it (4 + 4 + 4 = 12). For simpler centering, use mx-auto (margin: 0 auto) which works regardless of column count. Responsive offsets (offset-sm-2 offset-md-0) let you indent on mobile but not desktop. Offsets are cleaner than empty spacer columns.
<!-- offset: add left margin (empty columns) -->
<div class="row">
<div class="col-md-4 offset-md-4">
Centered (4 + 4 offset each side = 12)
</div>
</div>
<!-- offset only on one side -->
<div class="row">
<div class="col-md-6 offset-md-3">
Centered with more left space
</div>
</div>
<!-- responsive offset -->
<div class="row">
<div class="col-sm-8 offset-sm-2 offset-md-0 col-md-12">
Offset on mobile, none on desktop
</div>
</div>
<!-- margin utilities for auto centering -->
<div class="row">
<div class="col-md-6 mx-auto">
Auto-centered (margin: 0 auto)
</div>
</div>Row Columns & Auto Layout
row-cols-{n} sets how many equal-width columns per row — much cleaner than adding col-* to each child when they're all the same width. row-cols-2 means 2 columns per row; items wrap automatically. Responsive variants (row-cols-1 row-cols-md-3) change the count at breakpoints. Plain .col (no number) makes columns divide space equally. Use row-cols for card grids; use col-{n} when columns need specific proportions.
<!-- row-cols: set number of columns per row -->
<div class="row row-cols-2">
<div class="col">Item 1</div>
<div class="col">Item 2</div>
<div class="col">Item 3</div> <!-- wraps to new row -->
<div class="col">Item 4</div>
</div>
<!-- 2 per row automatically -->
<!-- responsive row-cols -->
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4">
<div class="col">Card</div>
<div class="col">Card</div>
<div class="col">Card</div>
<div class="col">Card</div>
</div>
<!-- auto-fit columns: .col without number -->
<div class="row">
<div class="col">Equal</div>
<div class="col">Equal</div>
<div class="col">Equal</div>
</div>
<!-- each takes 1/3, regardless of content -->Typography
Headings & Display
Bootstrap styles h1-h6 with rem-based sizes (h1 = 2.5rem, h6 = 1rem). The .h1-.h6 classes apply heading styles to any element. Display headings (display-1 to display-6) are larger and thinner — for hero sections. .lead makes a paragraph slightly larger and lighter — typically for the first paragraph. Headings have no top margin by default (Reboot removes it) to avoid layout gaps.
<!-- standard headings (h1-h6) -->
<h1>h1 heading</h1>
<h2>h2 heading</h2>
<h3>h3 heading</h3>
<!-- heading classes for matching styles -->
<p class="h1">Looks like h1</p>
<p class="h2">Looks like h2</p>
<!-- display headings (larger, thinner) -->
<h1 class="display-1">Display 1</h1>
<h1 class="display-2">Display 2</h1>
<h1 class="display-3">Display 3</h1>
<h1 class="display-4">Display 4</h1>
<h1 class="display-5">Display 5</h1>
<h1 class="display-6">Display 6</h1>
<!-- lead paragraph -->
<p class="lead">This is a lead paragraph.</p>Inline Text Elements
Bootstrap styles semantic HTML elements: <mark> (highlight), <del>/<s> (strikethrough), <ins>/<u> (underline), <small> (fine print). Text utilities provide styling: text-uppercase/lowercase/capitalize, fw-bold/light/normal, fst-italic, text-muted (gray). text-decoration-underline/line-through control underlines. These utilities are preferred over inline styles for consistency.
<!-- text styling -->
<p>You can use <mark>highlight</mark> text.</p>
<p><del>Deleted</del> and <s>strikethrough</s> text.</p>
<p><ins>Inserted</ins> and <u>underline</u> text.</p>
<p><small>Small text</small> for fine print.</p>
<p><strong>Bold</strong> and <em>italic</em> text.</p>
<!-- text utilities -->
<p class="text-decoration-underline">Underlined</p>
<p class="text-decoration-line-through">Line through</p>
<p class="text-lowercase">lowercase text</p>
<p class="text-uppercase">UPPERCASE TEXT</p>
<p class="text-capitalize">Capitalized Text</p>
<p class="fw-bold">Font weight bold</p>
<p class="fw-light">Font weight light</p>
<p class="fst-italic">Font style italic</p>
<p class="text-muted">Muted/gray text</p>Lists
list-unstyled removes default list styling (bullets and left padding) — useful for navigation menus. list-inline makes list items display inline (for tag clouds, breadcrumbs). Description lists (dl/dt/dd) get horizontal layout with the row class — dt on left, dd on right, aligned via the grid. Lists in Bootstrap have margin-top: 0 and margin-bottom: 1rem for consistent spacing.
<!-- unordered list -->
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<!-- unstyled list (no bullets, no padding) -->
<ul class="list-unstyled">
<li>No bullets</li>
<li>No left padding</li>
</ul>
<!-- inline list -->
<ul class="list-inline">
<li class="list-inline-item">Inline 1</li>
<li class="list-inline-item">Inline 2</li>
<li class="list-inline-item">Inline 3</li>
</ul>
<!-- description list -->
<dl>
<dt>Term</dt>
<dd>Definition</dd>
</dl>
<!-- horizontal description list -->
<dl class="row">
<dt class="col-sm-3">Term</dt>
<dd class="col-sm-9">Definition</dd>
</dl>Blockquotes & Code
Blockquotes use the <figure>/<blockquote>/<figcaption> structure for semantics. blockquote-footer styles the citation. Code styling: <code> for inline code, <pre><code> for code blocks, <kbd> for keyboard keys, <var> for variables. Always HTML-escape angle brackets in code (< for <). Blockquotes can be aligned with text-center/text-end. The kbd styling gives keys a keyboard-like appearance.
<!-- blockquote with source -->
<figure>
<blockquote class="blockquote">
<p>A well-known quote.</p>
</blockquote>
<figcaption class="blockquote-footer">
Someone famous in <cite title="Source Title">Source Title</cite>
</figcaption>
</figure>
<!-- blockquote alignment -->
<blockquote class="blockquote text-center">Centered</blockquote>
<blockquote class="blockquote text-end">Right-aligned</blockquote>
<!-- inline code -->
<p>Use <code><section></code> tag.</p>
<!-- code block -->
<pre><code><div class="container">
Hello
</div>
</code></pre>
<!-- variables -->
<var>y</var> = <var>mx</var> + <var>b</var>
<!-- keyboard input -->
<kbd>Ctrl</kbd> + <kbd>C</kbd>Text Alignment & Wrapping
Text alignment: text-start/center/end (Bootstrap 5 renamed left/right to start/end for RTL support). Responsive variants (text-md-start) change alignment at breakpoints. text-nowrap prevents wrapping; text-truncate adds ellipsis for overflow (needs a max-width or constrained container). text-break breaks long words. lh-* controls line-height. These utilities handle text flow without custom CSS.
<!-- text alignment -->
<p class="text-start">Left aligned (default)</p>
<p class="text-center">Center aligned</p>
<p class="text-end">Right aligned</p>
<!-- responsive alignment -->
<p class="text-center text-md-start">
Center on mobile, left on md+
</p>
<!-- text wrapping -->
<div class="text-wrap">This text wraps.</div>
<div class="text-nowrap">This text doesn't wrap.</div>
<!-- truncate with ellipsis -->
<div class="text-truncate" style="max-width: 200px;">
This long text will be truncated with an ellipsis...
</div>
<!-- word break -->
<p class="text-break">verylongwordwithoutspaces</p>
<!-- line height -->
<p class="lh-1">Line height 1</p>
<p class="lh-sm">Line height small</p>
<p class="lh-base">Line height base (default)</p>
<p class="lh-lg">Line height large</p>Tables
Basic Table
The .table class adds padding, borders, and horizontal dividers. Use <thead> for headers, <tbody> for data. scope='col' on <th> in the header and scope='row' on <th> in rows improve accessibility for screen readers. Tables are opt-in — without .table, you get unstyled HTML tables. Always use semantic table elements (thead, tbody, th, tr, td) for accessibility.
<!-- basic table with styling -->
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td>Alice</td>
<td>[email protected]</td>
</tr>
<tr>
<th scope="row">2</th>
<td>Bob</td>
<td>[email protected]</td>
</tr>
</tbody>
</table>Table Variants & Striped
Table modifiers: table-striped (alternating row colors), table-bordered (all borders), table-borderless (no borders), table-hover (row highlight on hover), table-sm (condensed padding), table-dark (dark theme). These can be combined: table table-striped table-hover. The dark variant inverts colors for dark backgrounds. table-sm halves the cell padding for denser data display.
<!-- striped rows -->
<table class="table table-striped">
<!-- alternating background colors -->
</table>
<!-- bordered table -->
<table class="table table-bordered">
<!-- borders on all sides -->
</table>
<!-- borderless table -->
<table class="table table-borderless">
<!-- no borders -->
</table>
<!-- hoverable rows -->
<table class="table table-hover">
<!-- row highlights on hover -->
</table>
<!-- small table -->
<table class="table table-sm">
<!-- condensed padding -->
</table>
<!-- dark table -->
<table class="table table-dark">
<!-- dark background -->
</table>Table Colors
Apply contextual classes to <tr> or <td>: table-primary, table-success, table-danger, table-warning, table-info, table-secondary, table-light, table-dark, table-active. These add subtle background tints for status indication. On dark tables, the same classes provide lighter variants. Use sparingly for meaningful status (success for completed, danger for errors) rather than decoration.
<!-- contextual row/cell colors -->
<table class="table">
<thead>
<tr class="table-primary"><th>Primary header</th></tr>
</thead>
<tbody>
<tr class="table-success">
<td>Success row (green)</td>
</tr>
<tr class="table-danger">
<td>Danger row (red)</td>
</tr>
<tr class="table-warning">
<td>Warning row (yellow)</td>
</tr>
<tr class="table-info">
<td>Info row (cyan)</td>
</tr>
<!-- individual cells -->
<tr>
<td class="table-active">Active cell</td>
<td>Normal cell</td>
</tr>
</tbody>
</table>Responsive Tables
Wrap tables in .table-responsive to enable horizontal scrolling on small screens (prevents layout breakage). table-responsive-sm/md/lg/xl/xxl scroll only below that breakpoint — the table fits normally on larger screens. Without responsive wrapping, wide tables overflow their container. The wrapper div is necessary because overflow can't be applied directly to <table>. Always use this for tables with many columns.
<!-- responsive table (horizontal scroll on small screens) -->
<div class="table-responsive">
<table class="table">
<!-- scrolls horizontally on small screens -->
<thead>...</thead>
<tbody>...</tbody>
</table>
</div>
<!-- responsive at specific breakpoints -->
<div class="table-responsive-sm">
<!-- scrolls below sm -->
</div>
<div class="table-responsive-md">
<!-- scrolls below md -->
</div>
<div class="table-responsive-lg">
<!-- scrolls below lg -->
</div>
<!-- always scrollable -->
<div class="table-responsive">...</div>Table Groups & Captions
caption-top moves the caption above the table (default is below). Captions improve accessibility for screen readers. Style thead/tbody/tfoot separately with table-light/table-dark. Vertical alignment: align-top, align-middle, align-bottom on cells or rows — useful when cells have different content heights. The tfoot element is styled like the thead by default but can be customized.
<!-- caption (accessibility, shown below table by default) -->
<table class="table caption-top">
<caption>List of users</caption>
<thead>
<tr><th>#</th><th>Name</th></tr>
</thead>
<tbody>
<tr><td>1</td><td>Alice</td></tr>
</tbody>
</table>
<!-- table groups for styling -->
<table class="table">
<thead class="table-light">
<tr><th>Light header</th></tr>
</thead>
<tbody>
<tr><td>Body</td></tr>
</tbody>
<tfoot class="table-dark">
<tr><td>Dark footer</td></tr>
</tfoot>
</table>
<!-- vertically aligned cells -->
<tr class="align-middle">
<td class="align-top">Top</td>
<td class="align-middle">Middle</td>
<td class="align-bottom">Bottom</td>
</tr>Forms
Form Controls
form-control styles text inputs, textareas, and file inputs. form-select styles dropdowns. Always pair inputs with <label> elements (with for/id matching) for accessibility. form-label adds appropriate spacing. disabled prevents interaction; readonly shows value without editing. mb-3 (margin-bottom) adds spacing between form groups. The .form-control class applies consistent height, padding, and focus styles.
<!-- text input -->
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" placeholder="Enter email">
</div>
<!-- textarea -->
<div class="mb-3">
<label for="bio" class="form-label">Bio</label>
<textarea class="form-control" id="bio" rows="3"></textarea>
</div>
<!-- select -->
<select class="form-select" aria-label="Default select">
<option selected>Open this select menu</option>
<option value="1">One</option>
<option value="2">Two</option>
</select>
<!-- disabled -->
<input class="form-control" type="text" placeholder="Disabled" disabled>
<input class="form-control" type="text" value="Readonly" readonly>Form Grid & Layout
Use the grid system (row/col) to lay out forms. For horizontal forms, pair col-form-label with col-* for label/input alignment. row-cols-lg-auto makes form elements size to their content (inline form) on large screens. align-items-center vertically aligns labels with inputs. The grid gives precise control over form layout at different breakpoints without custom CSS.
<!-- form using grid -->
<div class="row mb-3">
<label for="name" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="name">
</div>
</div>
<!-- inline form -->
<form class="row row-cols-lg-auto g-3 align-items-center">
<div class="col">
<input class="form-control" placeholder="Email">
</div>
<div class="col">
<input class="form-control" placeholder="Password">
</div>
<div class="col">
<button type="submit" class="btn btn-primary">Sign in</button>
</div>
</form>
<!-- horizontal form with column sizing -->
<form>
<div class="row mb-3">
<label class="col-2 col-form-label">Email</label>
<div class="col-10">
<input class="form-control" type="email">
</div>
</div>
</form>Input Groups
Input groups prepend or append text/buttons to inputs using input-group-text. Common for @ usernames, $ currency, .00 decimals, or search buttons. The input-group wrapper makes the addon and input appear as one connected control. Multiple inputs can share one addon. Buttons inside input groups use btn-outline-* for a connected look. Don't mix input groups with form-floating.
<!-- prepend text -->
<div class="input-group mb-3">
<span class="input-group-text">@</span>
<input type="text" class="form-control" placeholder="Username">
</div>
<!-- append text -->
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="Recipient's username">
<span class="input-group-text">@example.com</span>
</div>
<!-- prepend + append -->
<div class="input-group mb-3">
<span class="input-group-text">$</span>
<input type="text" class="form-control">
<span class="input-group-text">.00</span>
</div>
<!-- with button -->
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="Search">
<button class="btn btn-outline-secondary" type="button">Go</button>
</div>
<!-- multiple inputs -->
<div class="input-group">
<span class="input-group-text">First and last name</span>
<input type="text" class="form-control">
<input type="text" class="form-control">
</div>Floating Labels
Floating labels animate the label from inside the input to above it when the field is focused or has content. The label must come AFTER the input in HTML. A placeholder attribute is required (even if empty) for the floating behavior. This pattern saves space and looks modern. Don't combine with input-group. Floating labels work with inputs, textareas, and selects. They're a clean alternative to traditional labels for compact forms.
<!-- floating label (label floats up when focused/filled) -->
<div class="form-floating mb-3">
<input type="email" class="form-control" id="floatingInput" placeholder="[email protected]">
<label for="floatingInput">Email address</label>
</div>
<!-- floating textarea -->
<div class="form-floating">
<textarea class="form-control" id="floatingTextarea" placeholder="Leave a comment"></textarea>
<label for="floatingTextarea">Comments</label>
</div>
<!-- floating select -->
<div class="form-floating mb-3">
<select class="form-select" id="floatingSelect">
<option selected>Open this select menu</option>
<option value="1">One</option>
</select>
<label for="floatingSelect">Works with selects</label>
</div>
<!-- note: placeholder is REQUIRED for floating labels -->
<!-- the placeholder="..." must be present -->Validation
Bootstrap provides is-valid and is-invalid classes for validation styling, with valid-feedback and invalid-feedback for messages. The needs-validation class on a form, combined with novalidate (disables browser tooltips), lets you use Bootstrap's validation UI with custom JS. On submit, add was-validated to show all validation states. This works with HTML5 constraints (required, pattern, type) or custom server-side validation.
<!-- server-side validation classes -->
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control is-valid" id="username" value="valid_user">
<div class="valid-feedback">Looks good!</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control is-invalid" id="email">
<div class="invalid-feedback">Please enter a valid email.</div>
</div>
<!-- no validate on form to disable browser validation -->
<form class="needs-validation" novalidate>
<div class="mb-3">
<input type="text" class="form-control" required>
<div class="invalid-feedback">This field is required.</div>
</div>
<button class="btn btn-primary" type="submit">Submit</button>
</form>
<!-- JS: form.classList.add('was-validated') on submit -->Cards
Basic Card
Cards are flexible containers with optional header, body, and footer. card-img-top places an image at the top. card-title, card-subtitle, card-text style content. The width is controlled by the parent grid or a style attribute — cards default to full width. card-header and card-footer are for structured content. Cards replaced Bootstrap 3's panels, wells, and thumbnails with one unified component.
<div class="card" style="width: 18rem;">
<!-- optional image -->
<img src="..." class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
<p class="card-text">Some quick example text to build on the card title.</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</div>
</div>
<!-- card sections -->
<div class="card">
<div class="card-header">Header</div>
<div class="card-body">Body content</div>
<div class="card-footer text-muted">Footer</div>
</div>Card Content Types
Cards can contain various content types: images, text, list groups (use list-group-flush to remove borders and match card edges), and links (card-link adds spacing). Mix and match sections — card-body, list-group, card-header, card-footer — to build the exact card you need. list-group-flush integrates lists seamlessly into cards without double borders. This flexibility makes cards Bootstrap's most versatile component.
<!-- list group inside card -->
<div class="card" style="width: 18rem;">
<div class="card-header">Featured</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">Item 1</li>
<li class="list-group-item">Item 2</li>
<li class="list-group-item">Item 3</li>
</ul>
<div class="card-body">
<a href="#" class="card-link">Card link</a>
<a href="#" class="card-link">Another link</a>
</div>
</div>
<!-- kitchen sink (all content types) -->
<div class="card">
<img src="..." class="card-img-top">
<div class="card-body">
<h5 class="card-title">Title</h5>
<p class="card-text">Text</p>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">List item</li>
</ul>
<div class="card-body">
<a href="#" class="card-link">Link</a>
</div>
</div>Card Grid & Groups
card-group connects cards with shared borders and equal heights. For responsive card grids, use the grid system: row row-cols-1 row-cols-md-3 g-4 gives 1 column on mobile, 3 on desktop, with gap-4 spacing. Each card goes in a .col. The grid approach is more flexible than card-group — cards can have independent heights, and you control wrapping. Use card-group only when you need connected, equal-height cards.
<!-- card group (cards share border, equal height) -->
<div class="card-group">
<div class="card">
<img src="..." class="card-img-top">
<div class="card-body">
<h5 class="card-title">Card 1</h5>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">Card 2</h5>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">Card 3</h5>
</div>
</div>
</div>
<!-- cards have equal height, connected borders -->
<!-- using grid for responsive card layout -->
<div class="row row-cols-1 row-cols-md-3 g-4">
<div class="col">
<div class="card">...</div>
</div>
<div class="col">
<div class="card">...</div>
</div>
</div>Card Navigation
Add navigation to cards with nav-tabs or nav-pills in the card-header. card-header-tabs and card-header-pills integrate the nav flush with the header edges. Use nav-link active for the current tab, nav-link disabled for inactive. To make tabs functional (switch content), add data-bs-toggle='tab' and tab panes — Bootstrap's JS handles the switching. This pattern is common for dashboards and content cards with multiple views.
<!-- card with nav tabs -->
<div class="card text-center">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a class="nav-link active" href="#">Active</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
</div>
<div class="card-body">
<h5 class="card-title">Special title</h5>
<p class="card-text">Content for active tab.</p>
<a href="#" class="btn btn-primary">Go</a>
</div>
</div>
<!-- card with pills -->
<div class="card-header">
<ul class="nav nav-pills card-header-pills">
<li class="nav-item"><a class="nav-link active" href="#">Active</a></li>
</ul>
</div>Card Variants & Overlay
Card color variants: bg-{color} with text-white/text-dark for solid backgrounds; border-{color} with text-{color} for colored borders. card-img-overlay places text over an image (card-img fills the card, overlay content sits on top) — use text-bg-dark for readable text on images. text-center/text-start/text-end control text alignment. For dark image overlays, ensure sufficient contrast between text and image.
<!-- colored card -->
<div class="card text-white bg-primary">
<div class="card-header">Header</div>
<div class="card-body">
<h5 class="card-title">Primary card</h5>
<p class="card-text">Text on colored background.</p>
</div>
</div>
<!-- border colored card -->
<div class="card border-primary">
<div class="card-header border-primary">Header</div>
<div class="card-body text-primary">
<h5 class="card-title">Primary border</h5>
</div>
</div>
<!-- image overlay (text over image) -->
<div class="card text-bg-dark">
<img src="..." class="card-img">
<div class="card-img-overlay">
<h5 class="card-title">Overlay title</h5>
<p class="card-text">Text over the image.</p>
</div>
</div>
<!-- text alignment -->
<div class="card text-center">Centered text</div>
<div class="card text-end">Right-aligned</div>Modals
Basic Modal
Modals are triggered by data-bs-toggle='modal' with data-bs-target matching the modal's id. Structure: modal > modal-dialog > modal-content > modal-header/body/footer. btn-close is the X button (data-bs-dismiss='modal'). fade adds a transition. tabindex='-1' and aria-hidden are for accessibility. The modal is hidden by default and positioned with fixed positioning. Bootstrap JS handles showing, hiding, backdrop, and focus management.
<!-- Button to trigger -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal"
data-bs-target="#exampleModal">
Launch demo modal
</button>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1"
aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
Modal body text goes here.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>Modal Sizing & Scrolling
Modal sizes: modal-sm, default, modal-lg, modal-xl, modal-fullscreen. modal-fullscreen-{breakpoint}-down is fullscreen only below that breakpoint. modal-dialog-scrollable makes the body scroll (header/footer stay fixed) — essential for long content. modal-dialog-centered vertically centers the modal. These classes go on modal-dialog. Combine them: modal-dialog modal-dialog-centered modal-dialog-scrollable for a centered, scrollable modal.
<!-- sizes -->
<div class="modal-dialog modal-sm">Small</div>
<div class="modal-dialog">Default</div>
<div class="modal-dialog modal-lg">Large</div>
<div class="modal-dialog modal-xl">Extra large</div>
<div class="modal-dialog modal-fullscreen">Fullscreen</div>
<!-- responsive fullscreen -->
<div class="modal-dialog modal-fullscreen-sm-down">
<!-- fullscreen below sm, normal above -->
</div>
<!-- scrollable long content -->
<div class="modal-dialog modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-body">
<!-- long content scrolls within modal -->
</div>
</div>
</div>
<!-- vertically centered -->
<div class="modal-dialog modal-dialog-centered">
<!-- centered vertically in viewport -->
</div>Modal via JavaScript
Control modals via JS: create an instance with new bootstrap.Modal(el), then show()/hide()/toggle(). backdrop: 'static' prevents closing on backdrop click; keyboard: false disables Esc. Events: show.bs.modal, shown.bs.modal, hide.bs.modal, hidden.bs.modal (show = starting, shown = finished). Use shown.bs.modal to focus an input or load dynamic content. The static backdrop is useful for forms where accidental closure loses data.
<!-- HTML modal (hidden) -->
<div class="modal fade" id="myModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">Dynamic content here</div>
</div>
</div>
</div>
<script>
// create modal instance
const modalEl = document.getElementById('myModal')
const modal = new bootstrap.Modal(modalEl)
// show
modal.show()
// hide
modal.hide()
// toggle
modal.toggle()
// handle events
modalEl.addEventListener('shown.bs.modal', () => {
console.log('modal fully shown')
})
modalEl.addEventListener('hidden.bs.modal', () => {
console.log('modal fully hidden')
})
// pass options
const modal = new bootstrap.Modal(modalEl, {
backdrop: 'static', // don't close on backdrop click
keyboard: false // don't close on Esc
})
</script>Tooltips & Popovers
Tooltips show on hover/focus; popovers show on click. Both need JS initialization (Bootstrap doesn't auto-init for performance). data-bs-placement sets position (top/bottom/left/right). Popovers have title + content; data-bs-html='true' allows HTML. data-bs-trigger='focus' makes popovers dismissible (click outside). Tooltips and popovers use Popper.js (included in the bundle) for smart positioning. They're ideal for hints, definitions, and contextual info.
<!-- tooltip (hover/focus) -->
<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip"
data-bs-placement="top" title="Tooltip on top">
Hover me
</button>
<!-- positions: top, bottom, left, right -->
<button data-bs-toggle="tooltip" data-bs-placement="right"
title="Right tooltip">Right</button>
<!-- popover (click) -->
<button type="button" class="btn btn-lg btn-danger" data-bs-toggle="popover"
title="Popover title" data-bs-content="Content here">
Click to toggle popover
</button>
<!-- popover with HTML content -->
<button data-bs-toggle="popover" data-bs-html="true"
data-bs-content="<b>Bold</b> content">HTML popover</button>
<!-- dismissible popover -->
<button data-bs-toggle="popover" data-bs-trigger="focus"
title="Dismissible">Click outside to dismiss</button>
<script>
// MUST initialize tooltips and popovers
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
tooltipTriggerList.forEach(t => new bootstrap.Tooltip(t))
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
popoverTriggerList.forEach(p => new bootstrap.Popover(p))
</script>Offcanvas (Drawer)
Offcanvas is a slide-in drawer (like a modal but from the edge). offcanvas-start/end/top/bottom sets the slide direction. Structure mirrors modal: offcanvas-header/body with btn-close. data-bs-backdrop='static' prevents closing on backdrop click. text-bg-dark for dark variant. Offcanvas is ideal for mobile navigation, filters, or side panels. Unlike modals, it doesn't cover the full screen — it's an overlay drawer. Replace navbar-collapse with offcanvas for modern mobile menus.
<!-- trigger button -->
<button class="btn btn-primary" data-bs-toggle="offcanvas"
data-bs-target="#demoOffcanvas">
Open offcanvas
</button>
<!-- offcanvas drawer -->
<div class="offcanvas offcanvas-start" tabindex="-1" id="demoOffcanvas">
<div class="offcanvas-header">
<h5 class="offcanvas-title">Title</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
<p>Content for the offcanvas.</p>
</div>
</div>
<!-- positions: start (left), end (right), top, bottom -->
<div class="offcanvas offcanvas-end" id="rightDrawer">Right side</div>
<div class="offcanvas offcanvas-top" id="topDrawer">Top</div>
<!-- dark variant -->
<div class="offcanvas offcanvas-start text-bg-dark" id="darkDrawer">
Dark offcanvas
</div>
<!-- static backdrop (no close on click outside) -->
<div class="offcanvas" data-bs-backdrop="static">...</div>Alerts
Basic Alerts
Alerts display contextual feedback messages. alert-{variant} sets the color (primary, success, danger, warning, info, light, dark). role='alert' is for screen reader accessibility. alert-link styles links to match the alert color. Use alerts for: form validation feedback, action results (saved! deleted!), or important notices. Choose colors semantically: success for positive, danger for errors, warning for caution, info for neutral info.
<!-- alert variants -->
<div class="alert alert-primary" role="alert">Primary alert</div>
<div class="alert alert-secondary" role="alert">Secondary alert</div>
<div class="alert alert-success" role="alert">Success! Well done.</div>
<div class="alert alert-danger" role="alert">Danger! Something went wrong.</div>
<div class="alert alert-warning" role="alert">Warning! Check this.</div>
<div class="alert alert-info" role="alert">Info! Note this.</div>
<div class="alert alert-light" role="alert">Light alert</div>
<div class="alert alert-dark" role="alert">Dark alert</div>
<!-- with link -->
<div class="alert alert-primary" role="alert">
This is an alert with <a href="#" class="alert-link">a link</a>.
</div>Dismissible Alerts
alert-dismissible adds padding for the close button; btn-close with data-bs-dismiss='alert' closes it. fade show enables the fade-out transition. Once dismissed, the alert is removed from the DOM (not just hidden). Via JS, create an Alert instance and call close(). The closed.bs.alert event fires after removal. If you want to show it again, you must re-create the DOM element. For persistent dismissals (don't show again), store the state in localStorage.
<!-- dismissible alert -->
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<strong>Holy guacamole!</strong> You should check in on some of those fields.
<button type="button" class="btn-close" data-bs-dismiss="alert"
aria-label="Close"></button>
</div>
<!-- needs these classes:
- alert-dismissible: adds padding for close button
- fade show: enables transition
<!-- after dismissal, the alert is removed from DOM -->
<!-- via JavaScript -->
<script>
const alertEl = document.querySelector('.alert')
const alert = new bootstrap.Alert(alertEl)
alert.close() // closes and removes
</script>
<!-- events -->
alertEl.addEventListener('closed.bs.alert', () => {
console.log('alert was closed and removed')
})
</script>Alert with Icons & Content
Alerts can contain rich content: headings (alert-heading), paragraphs, dividers (hr), icons, and action buttons. For icons, use Bootstrap Icons (bi class) or SVGs with flex alignment. d-flex align-items-center vertically centers icon and text. Include action buttons (like 'Extend' for session warnings) alongside the dismiss button. Alerts aren't just for text — they're flexible containers for important, dismissible feedback.
<!-- alert with icon -->
<div class="alert alert-success d-flex align-items-center" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24">
<use xlink:href="#check-circle-fill"/>
</svg>
<div>
Operation completed successfully!
</div>
</div>
<!-- alert with more content -->
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">Error!</h4>
<p>There was a problem processing your request.</p>
<hr>
<p class="mb-0">Please try again or contact support.</p>
</div>
<!-- alert with action button -->
<div class="alert alert-warning alert-dismissible" role="alert">
Your session expires in 5 minutes.
<button type="button" class="btn btn-warning btn-sm ms-3">
Extend
</button>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>Toast Notifications
Toasts are lightweight notifications (like Android toasts). They auto-hide after a delay (default 5s). Wrap in toast-container for stacking. position-fixed with top-0 end-0 pins to top-right. aria-live='assertive' announces to screen readers. toast-header has icon, title, timestamp, close button; toast-body has the message. Unlike alerts, toasts don't block the UI — they're non-intrusive. Use for 'Saved!', 'New message', or background task completion.
<!-- toast container (top-right by default) -->
<div class="toast-container position-fixed top-0 end-0 p-3">
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<img src="..." class="rounded me-2" width="20">
<strong class="me-auto">Bootstrap</strong>
<small>11 mins ago</small>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">
Hello, world! This is a toast message.
</div>
</div>
</div>
<script>
// show via JS
const toastEl = document.querySelector('.toast')
const toast = new bootstrap.Toast(toastEl, {
delay: 5000, // auto-hide after 5s
autohide: true
})
toast.show()
// events
toastEl.addEventListener('shown.bs.toast', () => {
console.log('toast visible')
})
</script>Live Alert Region
For dynamically updated alerts (status messages, progress), use aria-live='polite' — screen readers announce changes without interrupting. aria-live='assertive' announces immediately (for critical errors). aria-atomic='true' reads the entire content on change (not just the changed part). role='alert' is implicitly assertive. This accessibility pattern ensures screen reader users hear dynamic updates. Combine with alert classes for visual styling.
<!-- aria-live for dynamic alerts -->
<div class="alert alert-info" role="alert" aria-live="polite"
aria-atomic="true" id="statusAlert">
<!-- content updated dynamically -->
</div>
<script>
// update alert content
function showStatus(message, type = 'info') {
const alert = document.getElementById('statusAlert')
alert.className = 'alert alert-' + type
alert.textContent = message
}
showStatus('Loading...', 'info')
// later:
showStatus('Data loaded!', 'success')
<!-- aria-live="polite" announces updates without interrupting -->
<!-- aria-live="assertive" interrupts immediately (urgent) -->
<!-- for form errors, use role="alert" (assertive by default) -->
<div role="alert" class="alert alert-danger" id="formError">
Please fix the errors below.
</div>
</script>Badges
Basic Badges
Badges are small count/label indicators. bg-{color} sets the background; add text-dark on light backgrounds (warning, info, light) for contrast. Badges scale relative to their parent element's font-size — an h1 badge is larger than a p badge. Use badges for: unread counts, status labels, 'New' tags, version numbers. They're inline elements that fit within text flow.
<!-- inline badges -->
<h1>Heading <span class="badge bg-secondary">New</span></h1>
<h2>Heading <span class="badge bg-secondary">New</span></h2>
<h3>Heading <span class="badge bg-secondary">New</span></h3>
<!-- colored badges -->
<span class="badge bg-primary">Primary</span>
<span class="badge bg-secondary">Secondary</span>
<span class="badge bg-success">Success</span>
<span class="badge bg-danger">Danger</span>
<span class="badge bg-warning text-dark">Warning</span>
<span class="badge bg-info text-dark">Info</span>
<span class="badge bg-light text-dark">Light</span>
<span class="badge bg-dark">Dark</span>
<!-- badge sizing scales with parent -->
<h1>Big <span class="badge bg-primary">Badge</span></h1>
<p>Small <span class="badge bg-primary">Badge</span></p>Pill Badges & Rounded
rounded-pill makes fully rounded badges (pill shape). rounded-0 to rounded-3 control the border-radius scale. rounded-circle with padding creates circular badges — useful for notification counts. For perfect circles with single digits, set equal width/height and use d-flex align-items-center justify-content-center. Pill badges are common for tags and status indicators.
<!-- pill badges (more rounded) -->
<span class="badge rounded-pill bg-primary">Primary</span>
<span class="badge rounded-pill bg-success">Success</span>
<span class="badge rounded-pill bg-danger">Danger</span>
<!-- rounded variations -->
<span class="badge rounded-0 bg-primary">Not rounded</span>
<span class="badge rounded-1 bg-primary">Slightly rounded</span>
<span class="badge rounded-2 bg-primary">Default rounded</span>
<span class="badge rounded-3 bg-primary">More rounded</span>
<span class="badge rounded-pill bg-primary">Full pill</span>
<!-- circle badge (fixed size, centered text) -->
<span class="badge rounded-circle bg-primary p-2">5</span>
<!-- needs width/height for perfect circle -->Badges in Buttons & Nav
Badges in buttons: position-relative on the button, position-absolute on the badge with top-0 start-100 translate-middle to place it at the top-right corner. visually-hidden text inside the badge provides context for screen readers. Badges in nav links and list items show counts — use justify-content-between to push them right. The '99+' pattern is common when counts exceed display limits.
<!-- badge inside button -->
<button type="button" class="btn btn-primary position-relative">
Inbox
<span class="position-absolute top-0 start-100 translate-middle
badge rounded-pill bg-danger">
99+
</span>
</button>
<!-- badge with position outside button -->
<button class="btn btn-primary position-relative">
Messages
<span class="position-absolute top-0 start-100 translate-middle
badge rounded-pill bg-danger">
9
<span class="visually-hidden">unread messages</span>
</span>
</button>
<!-- badge in nav link -->
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" href="#">
Profile
<span class="badge bg-secondary">3</span>
</a>
</li>
</ul>
<!-- badge in list item -->
<li class="list-group-item d-flex justify-content-between align-items-center">
Messages
<span class="badge bg-primary rounded-pill">14</span>
</li>Badge Indicators (Dot)
Dot badges (no text, just a colored circle) indicate status without a count — like 'unread' or 'online'. Use p-1 with rounded-circle for a small dot, border-light to separate from the background. Position with the same translate-middle technique. Online status dots (green = online, gray = offline) go at the bottom corner of avatars. Badges can also wrap icons (Bootstrap Icons: bi bi-star-fill) with text for ratings.
<!-- status dot badge -->
<button class="btn btn-primary position-relative">
Notifications
<span class="position-absolute top-0 start-100 translate-middle
p-1 bg-danger border border-light rounded-circle">
<span class="visually-hidden">New alerts</span>
</span>
</button>
<!-- small dot (no text, just indicator) -->
<span class="position-absolute top-0 start-100 translate-middle
p-1 bg-danger border border-light rounded-circle"></span>
<!-- online status indicator -->
<span class="position-relative d-inline-flex">
<img src="avatar.jpg" class="rounded-circle" width="40">
<span class="position-absolute bottom-0 end-0
bg-success border border-light rounded-circle p-1">
</span>
</span>
<!-- green dot = online -->
<!-- badge with icon -->
<span class="badge bg-primary">
<i class="bi bi-star-fill"></i> 4.5
</span>Spinner Border & Grow
Spinners indicate loading state. spinner-border is a rotating ring; spinner-grow is a pulsating circle. text-* colors them. spinner-border-sm/grow-sm are smaller. Custom sizes via width/height CSS. Always include role='status' with visually-hidden text for accessibility. In buttons, use spinner-border-sm with disabled to show a loading action. Spinners are CSS-only (no JS) — they animate via border and opacity transitions.
<!-- border spinner (rotating border) -->
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<!-- colored spinner -->
<div class="spinner-border text-primary"></div>
<div class="spinner-border text-success"></div>
<div class="spinner-border text-danger"></div>
<!-- grow spinner (pulsating circle) -->
<div class="spinner-grow" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<!-- sizes -->
<div class="spinner-border spinner-border-sm">Small</div>
<div class="spinner-grow spinner-grow-sm">Small grow</div>
<!-- custom size via CSS -->
<div class="spinner-border" style="width: 3rem; height: 3rem;"></div>
<!-- spinner in button -->
<button class="btn btn-primary" disabled>
<span class="spinner-border spinner-border-sm"></span>
Loading...
</button>
<!-- spinner with text -->
<div class="d-flex align-items-center">
<strong>Loading...</strong>
<div class="spinner-border ms-auto"></div>
</div>Progress & Spinners
Basic Progress Bar
Progress bars show task completion. Set the width as a percentage via inline style. role='progressbar' with aria-valuenow/min/max is for accessibility. The height is set on the .progress container (default 1rem). bg-{color} on the bar changes its color. Label text inside the bar is optional — omit it for minimal bars. Always set width via style (not class) since values are dynamic.
<!-- basic progress bar -->
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 50%"
aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">
50%
</div>
</div>
<!-- without label -->
<div class="progress">
<div class="progress-bar" style="width: 75%" role="progressbar"
aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<!-- height (set on container) -->
<div class="progress" style="height: 1px;">
<div class="progress-bar" style="width: 25%"></div>
</div>
<div class="progress" style="height: 20px;">
<div class="progress-bar" style="width: 25%"></div>
</div>
<!-- background -->
<div class="progress">
<div class="progress-bar bg-success" style="width: 25%">25%</div>
</div>Striped & Animated Progress
progress-bar-striped adds diagonal stripes. progress-bar-animated makes the stripes move (for active/in-progress tasks). Only use animation for currently-running tasks — static bars for completed. Update progress via JS by changing style.width and aria-valuenow. The animation uses CSS — no JS needed for the movement. Combine bg-* with striped for colored striped bars.
<!-- striped progress bar -->
<div class="progress">
<div class="progress-bar progress-bar-striped" style="width: 50%">
50%
</div>
</div>
<!-- animated stripes (for in-progress tasks) -->
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated"
style="width: 75%" role="progressbar">
75%
</div>
</div>
<!-- colored striped -->
<div class="progress">
<div class="progress-bar progress-bar-striped bg-success" style="width: 100%">
Complete
</div>
</div>
<!-- update via JS -->
<div class="progress">
<div class="progress-bar" id="myBar" style="width: 0%"></div>
</div>
<script>
const bar = document.getElementById('myBar')
bar.style.width = '60%' // update progress
bar.setAttribute('aria-valuenow', '60')
</script>Multiple Progress Bars
Multiple progress-bar divs inside one .progress container stack side by side — each takes its percentage of the total width. This is useful for showing composition (e.g., storage: 25% photos, 30% videos, 20% apps). The bars share the same height and sit adjacent without gaps. Total should ideally sum to 100% (or less for incomplete). Each can have its own color and label.
<!-- stacked bars in one container -->
<div class="progress">
<div class="progress-bar" style="width: 15%">15%</div>
<div class="progress-bar bg-success" style="width: 30%">30%</div>
<div class="progress-bar bg-info" style="width: 20%">20%</div>
</div>
<!-- total: 65% filled, 35% empty -->
<!-- stacked with different colors -->
<div class="progress" style="height: 30px;">
<div class="progress-bar bg-danger" style="width: 25%">Errors</div>
<div class="progress-bar bg-warning" style="width: 25%">Warnings</div>
<div class="progress-bar bg-success" style="width: 50%">Success</div>
</div>
<!-- each bar takes its percentage of the full width -->
<!-- they sit side by side, not overlapping -->Placeholder Loading
Placeholders (skeleton loading) show gray blocks where content will appear — better than spinners for content-heavy pages. placeholder-glow creates a pulsing shimmer; placeholder-wave creates a moving wave. col-* sets the width of each block. For skeleton cards, use placeholder inside card components with aria-hidden='true'. This gives users a preview of the layout before data loads, reducing perceived wait time.
<!-- placeholder (gray animated blocks) -->
<div class="placeholder-glow">
<span class="placeholder col-12"></span>
<span class="placeholder col-12"></span>
<span class="placeholder col-8"></span>
</div>
<!-- wave animation -->
<div class="placeholder-wave">
<span class="placeholder col-12"></span>
</div>
<!-- placeholder card (skeleton loading) -->
<div class="card" aria-hidden="true">
<div class="card-body">
<h5 class="card-title placeholder-glow">
<span class="placeholder col-6"></span>
</h5>
<p class="card-text placeholder-glow">
<span class="placeholder col-7"></span>
<span class="placeholder col-4"></span>
<span class="placeholder col-4"></span>
<span class="placeholder col-6"></span>
</p>
<a class="btn btn-primary disabled placeholder col-6"></a>
</div>
</div>Range Slider
form-range styles the native range input (slider). Set min, max, and step attributes for the range and increment. The slider is full-width by default. For a value display, use oninput to update a label as the user drags. Disabled greys it out. Range inputs are good for volume, opacity, or any continuous value where exact precision isn't needed. Always pair with a label for accessibility.
<!-- range input -->
<label for="customRange1" class="form-label">Example range</label>
<input type="range" class="form-range" id="customRange1" min="0" max="5" step="0.5">
<!-- with value display -->
<div class="d-flex">
<input type="range" class="form-range me-2" id="range"
min="0" max="100" oninput="document.getElementById('val').textContent = this.value">
<span id="val" class="badge bg-primary">50</span>
</div>
<!-- disabled -->
<input type="range" class="form-range" disabled>
<!-- min, max, step -->
<input type="range" class="form-range" min="-10" max="10" step="2" value="0">Utility Classes
Spacing (Margin & Padding)
Spacing utilities follow {property}{sides}-{size}. m = margin, p = padding. Sides: t/b (top/bottom), s/e (start/end — left/right in LTR), x (left+right), y (top+bottom), blank (all). Sizes 0-5 map to the spacing scale (0, 0.25rem, 0.5rem, 1rem, 1.5rem, 3rem). mx-auto centers block elements. Responsive: add breakpoint (mt-md-3). Bootstrap 5 renamed left/right to start/end for RTL support.
<!-- format: {property}{sides}-{size} -->
<!-- property: m (margin), p (padding) -->
<!-- sides: t (top), b (bottom), s (start/left), e (end/right), x (both), y (both), blank (all) -->
<!-- size: 0, 1, 2, 3, 4, 5, auto -->
<div class="mt-3">margin-top: 1rem</div>
<div class="mb-4">margin-bottom: 1.5rem</div>
<div class="ms-2">margin-left: 0.5rem (start)</div>
<div class="me-2">margin-right: 0.5rem (end)</div>
<div class="mx-auto">margin: 0 auto (centered)</div>
<div class="my-5">margin top+bottom: 3rem</div>
<div class="p-3">padding: 1rem (all sides)</div>
<div class="px-4">padding left+right: 1.5rem</div>
<div class="pt-2">padding-top: 0.5rem</div>
<!-- responsive: {property}{sides}-{breakpoint}-{size} -->
<div class="mt-0 mt-md-3">No margin on mobile, 1rem on md+</div>
<div class="p-2 p-lg-4">Small padding, larger on lg+</div>Colors & Backgrounds
text-{color} sets text color; bg-{color} sets background. text-bg-{color} (Bootstrap 5.2+) automatically sets contrasting text color for the background. bg-gradient adds a subtle gradient. Opacity: text-opacity-50 or bg-opacity-50 (10%, 25%, 50%, 75%, 100%). text-muted is gray (secondary text). Always ensure sufficient contrast between text and background — use text-white on dark backgrounds. Colors carry semantic meaning (success=green, danger=red).
<!-- text colors -->
<p class="text-primary">Primary text</p>
<p class="text-success">Success text</p>
<p class="text-danger">Danger text</p>
<p class="text-muted">Muted text</p>
<p class="text-white bg-dark">White on dark</p>
<!-- background colors -->
<div class="bg-primary text-white">Primary bg</div>
<div class="bg-success text-white">Success bg</div>
<div class="bg-light">Light bg</div>
<div class="bg-dark text-white">Dark bg</div>
<!-- gradient background -->
<div class="bg-primary bg-gradient text-white p-3">
Gradient background
</div>
<!-- opacity -->
<div class="text-primary text-opacity-50">50% opacity text</div>
<div class="bg-primary bg-opacity-50">50% opacity bg</div>
<!-- text color with background theme -->
<div class="text-bg-primary">Primary (auto text color)</div>Display & Flexbox
d-* sets display property (d-none hides elements). Responsive d-{breakpoint}-* changes display at breakpoints — d-none d-md-block hides on mobile, shows on desktop. d-flex enables flexbox; flex-direction, justify-content-*, align-items-*, and flex-wrap control flex layout. These utilities replace most custom CSS for layout. d-md-flex activates flex only at md+. Combine for complex responsive layouts without media queries.
<!-- display utilities -->
<div class="d-none">display: none</div>
<div class="d-inline">display: inline</div>
<div class="d-inline-block">display: inline-block</div>
<div class="d-block">display: block</div>
<div class="d-flex">display: flex</div>
<div class="d-inline-flex">display: inline-flex</div>
<!-- responsive display -->
<div class="d-none d-md-block">Hidden on mobile, block on md+</div>
<div class="d-block d-md-none">Visible only below md</div>
<!-- flex direction -->
<div class="d-flex flex-column">Column</div>
<div class="d-flex flex-row-reverse">Reversed row</div>
<!-- justify content -->
<div class="d-flex justify-content-center">Center</div>
<div class="d-flex justify-content-between">Space between</div>
<div class="d-flex justify-content-around">Space around</div>
<!-- align items -->
<div class="d-flex align-items-center">Vertically centered</div>
<div class="d-flex align-items-end">Bottom aligned</div>
<!-- flex wrap -->
<div class="d-flex flex-wrap">Wrap</div>
<div class="d-flex flex-nowrap">No wrap</div>Position & Floats
position-* sets CSS position. position-sticky with top-0 creates a sticky header (sticks when scrolled). top/bottom/start/end (0 or 50%) set insets. translate-middle centers elements (combine top-50 start-50 for perfect centering). Floats (float-start/end) are legacy — use flexbox for layout. clearfix clears floated children. fixed-top/fixed-bottom pin elements to the viewport. Sticky is preferred over fixed for navbars as it doesn't overlap content initially.
<!-- position -->
<div class="position-static">Static (default)</div>
<div class="position-relative">Relative (offset parent)</div>
<div class="position-absolute">Absolute</div>
<div class="position-fixed">Fixed (viewport)</div>
<div class="position-sticky top-0">Sticky (sticks at top)</div>
<!-- positioning with insets -->
<div class="position-absolute top-0 start-0">Top-left</div>
<div class="position-absolute top-0 end-0">Top-right</div>
<div class="position-absolute bottom-0 start-50">Bottom-center</div>
<!-- center with translate -->
<div class="position-absolute top-50 start-50 translate-middle">
Perfectly centered
</div>
<!-- floats (use flexbox instead when possible) -->
<div class="float-start">Float left</div>
<div class="float-end">Float right</div>
<div class="clearfix">Clear floats</div>
<!-- fixed top/bottom -->
<div class="fixed-top">Pinned to top</div>
<div class="fixed-bottom">Pinned to bottom</div>Borders, Shadows & Radius
border adds/removes borders on specific sides. border-{color} colors them; border-{1-5} sets width. rounded-* controls border-radius: rounded-0 (none), rounded (default), rounded-3 (larger), rounded-pill (capsule), rounded-circle (50%). rounded-{side} rounds only specific corners. shadow-* adds box shadows: none, sm, default, lg. Shadows add depth — use shadow-sm for subtle elevation, shadow-lg for modals/floating elements. These utilities handle most visual styling without custom CSS.
<!-- borders -->
<div class="border">All borders</div>
<div class="border-top">Top only</div>
<div class="border border-0">No borders</div>
<div class="border-top-0">Remove top</div>
<!-- border color -->
<div class="border border-primary">Primary border</div>
<div class="border border-danger">Danger border</div>
<!-- border width -->
<div class="border border-2">2px border</div>
<div class="border border-4">4px border</div>
<!-- border radius -->
<div class="rounded">Default radius</div>
<div class="rounded-0">No radius</div>
<div class="rounded-3">Larger radius</div>
<div class="rounded-pill">Pill shape</div>
<div class="rounded-circle">Circle</div>
<div class="rounded-top">Top corners only</div>
<!-- shadows -->
<div class="shadow-none">No shadow</div>
<div class="shadow-sm">Small shadow</div>
<div class="shadow">Regular shadow</div>
<div class="shadow-lg">Large shadow</div>Images & Figures
Responsive Images
img-fluid makes images responsive (max-width: 100%, height: auto) — they never overflow their container. w-100 forces full width. figure/figure-img/figure-caption provide semantic image-with-caption (better than div + p). float-start/end aligns images; mx-auto d-block centers. rounded adds rounded corners. Always include alt text for accessibility — describe the image for screen readers. For decorative images, use alt=''.
<!-- responsive image (scales with parent) -->
<img src="photo.jpg" class="img-fluid" alt="Responsive image">
<!-- max-width: 100%; height: auto; -->
<!-- full-width image -->
<img src="banner.jpg" class="img-fluid w-100" alt="Banner">
<!-- image with figure -->
<figure class="figure">
<img src="photo.jpg" class="figure-img img-fluid rounded" alt="...">
<figcaption class="figure-caption text-end">
A caption for the image.
</figcaption>
</figure>
<!-- image alignment -->
<img src="..." class="rounded float-start" alt="Left">
<img src="..." class="rounded float-end" alt="Right">
<img src="..." class="rounded mx-auto d-block" alt="Centered">
<!-- center with text-center on parent -->
<div class="text-center">
<img src="..." class="rounded" alt="Centered">
</div>Image Shapes & Thumbnails
Image shapes: rounded (rounded corners), rounded-circle (circular — works best on square images), rounded-pill (capsule). img-thumbnail adds a border, padding, and rounded corners — gives a photo-print look. Combine img-thumbnail with rounded-circle for a circular thumbnail. For avatars, set explicit width/height to reserve space and prevent layout shift. The shape classes work on any element, not just images — useful for video thumbnails or profile pictures.
<!-- rounded corners -->
<img src="..." class="rounded" alt="Rounded">
<!-- fully rounded (circle) -->
<img src="..." class="rounded-circle" alt="Circle">
<!-- thumbnail (border + padding) -->
<img src="..." class="img-thumbnail" alt="Thumbnail">
<!-- adds padding, border, rounded corners -->
<!-- pill shape -->
<img src="..." class="rounded-pill" alt="Pill">
<!-- combination: thumbnail + circle -->
<img src="..." class="img-thumbnail rounded-circle" alt="Circle thumb">
<!-- avatar sizes (set width/height) -->
<img src="avatar.jpg" class="rounded-circle" width="40" height="40" alt="Avatar">
<img src="avatar.jpg" class="rounded-circle" width="100" height="100" alt="Large avatar">Figure & Captions
figure is the semantic HTML element for images with captions — better for accessibility than divs. figure-img styles the image (combine with img-fluid and rounded). figure-caption styles the caption text (muted by default). text-start/center/end aligns the caption. Wrap the image in an <a> for clickable enlargements. Figures are the correct semantic choice for editorial content, documentation, and galleries.
<!-- figure with caption -->
<figure class="figure">
<img src="photo.jpg" class="figure-img img-fluid rounded" alt="Photo">
<figcaption class="figure-caption">
Caption describing the image.
</figcaption>
</figure>
<!-- right-aligned caption -->
<figure class="figure">
<img src="photo.jpg" class="figure-img img-fluid rounded" alt="Photo">
<figcaption class="figure-caption text-end">
Right-aligned caption.
</figcaption>
</figure>
<!-- centered caption -->
<figcaption class="figure-caption text-center">Centered</figcaption>
<!-- figure with link -->
<figure class="figure">
<a href="full-size.jpg">
<img src="thumb.jpg" class="figure-img img-fluid rounded" alt="Click to enlarge">
</a>
<figcaption class="figure-caption">Click to view full size</figcaption>
</figure>Background Images
Bootstrap doesn't have background-image utilities (images are content-dependent), so use inline styles. background-size: cover fills the container without distortion; background-position: center focuses the center. For text over images, add a gradient overlay (dark at the bottom) for readability. position-relative on a wrapper with position-absolute layers lets you stack image, overlay, and text. This is the pattern for hero sections and image cards.
<!-- background image via inline style -->
<div style="background-image: url('bg.jpg');
background-size: cover;
background-position: center;
height: 400px;">
<div class="d-flex align-items-center justify-content-center h-100">
<h1 class="text-white">Hero text over image</h1>
</div>
</div>
<!-- parallax-like fixed background -->
<div style="background-image: url('bg.jpg');
background-attachment: fixed;
background-size: cover;
height: 300px;">
</div>
<!-- gradient overlay for text readability -->
<div class="position-relative">
<img src="bg.jpg" class="img-fluid" alt="">
<div class="position-absolute top-0 start-0 w-100 h-100"
style="background: linear-gradient(to bottom, transparent, rgba(0,0,0,0.7));">
</div>
<div class="position-absolute bottom-0 start-0 p-4 text-white">
<h2>Text over gradient</h2>
</div>
</div>Image Grid & Gallery
Image grids use the standard row/col system with g-* for gutters. For equal-height images regardless of aspect ratio, set a fixed height container and use object-fit: cover on the img with w-100 h-100. row-cols-* creates equal-width columns without specifying col-* on each item. For true masonry (variable heights, no gaps), CSS columns or JavaScript libraries are needed — Bootstrap's grid doesn't do masonry natively. img-fluid ensures images never overflow their grid cells.
<!-- responsive image grid -->
<div class="row g-2">
<div class="col-6 col-md-4">
<img src="1.jpg" class="img-fluid rounded" alt="">
</div>
<div class="col-6 col-md-4">
<img src="2.jpg" class="img-fluid rounded" alt="">
</div>
<div class="col-6 col-md-4">
<img src="3.jpg" class="img-fluid rounded" alt="">
</div>
<div class="col-6 col-md-4">
<img src="4.jpg" class="img-fluid rounded" alt="">
</div>
</div>
<!-- equal-height images with object-fit -->
<div class="row g-3">
<div class="col-md-4">
<div style="height: 200px;">
<img src="tall.jpg" class="w-100 h-100"
style="object-fit: cover;" alt="">
</div>
</div>
</div>
<!-- masonry-like with columns -->
<div class="row row-cols-1 row-cols-md-3 g-3">
<div class="col"><img src="1.jpg" class="img-fluid rounded"></div>
<div class="col"><img src="2.jpg" class="img-fluid rounded"></div>
</div>Dropdowns
Basic Dropdown
Dropdowns need: a trigger (button or anchor with data-bs-toggle='dropdown') and a ul.dropdown-menu with dropdown-item elements. Items can be <a> (for navigation) or <button> (for actions). aria-expanded reflects the open state. The dropdown is positioned below the trigger by default (Popper.js handles positioning). Clicking outside closes it. The JS bundle (including Popper) is required for dropdowns to work.
<!-- dropdown -->
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button"
data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else</a></li>
</ul>
</div>
<!-- dropdown with button (not link) items -->
<ul class="dropdown-menu">
<li><button class="dropdown-item" type="button">Action</button></li>
<li><button class="dropdown-item" type="button">Another</button></li>
</ul>
<!-- via anchor tag (for non-button triggers) -->
<a class="btn btn-secondary dropdown-toggle" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
Link dropdown
</a>Dropdown Menu Content
Dropdown menus can contain: dropdown-header (section title), dropdown-divider (separator line), dropdown-item (links/buttons), dropdown-item-text (non-clickable text), active/disabled items. Forms inside dropdowns: add p-4 padding and use the dropdown-menu class on the form itself. This is great for login dropdowns or filter forms. Group related items with headers and dividers for scannable menus.
<ul class="dropdown-menu">
<!-- header -->
<li><h6 class="dropdown-header">Dropdown header</h6></li>
<!-- items -->
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<!-- divider -->
<li><hr class="dropdown-divider"></li>
<!-- active item -->
<li><a class="dropdown-item active" href="#">Active</a></li>
<!-- disabled item -->
<li><a class="dropdown-item disabled">Disabled</a></li>
<!-- text item -->
<li><span class="dropdown-item-text">Just text</span></li>
</ul>
<!-- dropdown with form -->
<form class="dropdown-menu p-4">
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" class="form-control" placeholder="[email protected]">
</div>
<button type="submit" class="btn btn-primary">Sign in</button>
</form>Dropdown Alignment
dropdown-menu-end right-aligns the menu. dropdown-menu-{breakpoint}-end aligns right only at that breakpoint. dropup/dropend/dropstart change the opening direction (use dropup for menus at the bottom of the page). data-bs-offset='x,y' shifts the menu position. data-bs-auto-close='outside' keeps the menu open when clicking inside (useful for form dropdowns). These options handle most positioning needs without custom CSS.
<!-- default: aligned left below trigger -->
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown">
Left-aligned
</button>
<ul class="dropdown-menu">...</ul>
</div>
<!-- right-aligned menu -->
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown">
Right-aligned
</button>
<ul class="dropdown-menu dropdown-menu-end">...</ul>
</div>
<!-- responsive alignment -->
<ul class="dropdown-menu dropdown-menu-lg-end">
<!-- left on mobile, right on lg+ -->
</ul>
<!-- dropup (menu opens above) -->
<div class="dropup">
<button class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown">
Dropup
</button>
<ul class="dropdown-menu">...</ul>
</div>
<!-- dropend (right) / dropstart (left) -->
<div class="dropend">...</div>
<div class="dropstart">...</div>
<!-- menu offset -->
<button data-bs-toggle="dropdown" data-bs-offset="10,20">
Offset menu
</button>Dropdown via JavaScript
Control dropdowns via JS: new bootstrap.Dropdown(el) creates an instance. show()/hide()/toggle() control it programmatically. autoClose options: true (default, closes on any click), 'inside' (closes only on item click), 'outside' (closes on outside click), false (never auto-close — manual close only). Events: show/shown/hide/hidden.bs.dropdown. Use shown.bs.dropdown to load dynamic content or focus a specific item. This is useful for dynamic, data-driven dropdowns.
<!-- HTML -->
<div class="dropdown">
<button class="btn btn-secondary" id="dropdownBtn">
Toggle via JS
</button>
<ul class="dropdown-menu" id="myDropdown">
<li><a class="dropdown-item" href="#">Action</a></li>
</ul>
</div>
<script>
const dropdownEl = document.getElementById('myDropdown')
const dropdown = new bootstrap.Dropdown(dropdownEl, {
// options
offset: '10,0',
autoClose: true, // true | 'inside' | 'outside' | false
boundary: 'clippingParents'
})
// methods
dropdown.show()
dropdown.hide()
dropdown.toggle()
// events
dropdownEl.addEventListener('show.bs.dropdown', () => {
console.log('about to show')
})
dropdownEl.addEventListener('shown.bs.dropdown', () => {
console.log('fully shown')
})
dropdownEl.addEventListener('hidden.bs.dropdown', () => {
console.log('fully hidden')
})
</script>Split Dropdown Buttons
Split dropdown buttons separate the action (left button) from the menu toggle (small arrow button). dropdown-toggle-split reduces the toggle button's padding to a narrow arrow. visually-hidden text ('Toggle Dropdown') ensures screen reader accessibility. The two buttons share the same color class. Split buttons are ideal for 'Save' (primary action) + menu (Save As, Export, etc.). They work with all sizes (btn-lg/sm) and directions (dropup, dropend).
<!-- split button dropdown -->
<div class="btn-group">
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another</a></li>
</ul>
</div>
<!-- split with different sizes -->
<div class="btn-group">
<button class="btn btn-success btn-lg">Large</button>
<button class="btn btn-success btn-lg dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle</span>
</button>
<ul class="dropdown-menu">...</ul>
</div>
<!-- dropup split -->
<div class="btn-group dropup">
<button class="btn btn-secondary">Dropup</button>
<button class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle</span>
</button>
<ul class="dropdown-menu">...</ul>
</div>Collapse & Accordion
Basic Collapse
Collapse toggles element visibility with a height animation. Trigger: data-bs-toggle='collapse' with data-bs-target (button) or href (anchor) pointing to the collapsible element's id. .collapse is hidden; .collapse.show is visible. aria-expanded on the trigger reflects state; aria-controls links trigger to target. Use buttons for actions, anchors for navigation. The animation smoothly transitions height from 0 to auto.
<!-- collapse trigger (button) -->
<p>
<button class="btn btn-primary" type="button"
data-bs-toggle="collapse" data-bs-target="#collapseExample"
aria-expanded="false" aria-controls="collapseExample">
Toggle collapse
</button>
</p>
<div class="collapse" id="collapseExample">
<div class="card card-body">
This content is hidden by default and shown when the button is clicked.
</div>
</div>
<!-- collapse via anchor -->
<a class="btn btn-primary" data-bs-toggle="collapse"
href="#collapseExample2" role="button" aria-expanded="false"
aria-controls="collapseExample2">
Link trigger
</a>
<div class="collapse" id="collapseExample2">
<div class="card card-body">Content</div>
</div>
<!-- shown by default -->
<div class="collapse show" id="alwaysShown">
<div class="card card-body">Shown initially</div>
</div>Accordion
The accordion is a group of collapses where data-bs-parent ensures only one is open at a time (opening one closes others). Structure: accordion > accordion-item > accordion-header (with accordion-button) + accordion-collapse (with accordion-body). The first item can have 'show' class to start expanded. 'collapsed' class on the button indicates the collapsed state (rotates the arrow). aria-expanded and aria-controls are essential for accessibility. The parent attribute creates the exclusive-open behavior.
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
<button class="accordion-button" type="button"
data-bs-toggle="collapse" data-bs-target="#collapseOne"
aria-expanded="true" aria-controls="collapseOne">
Accordion Item #1
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show"
aria-labelledby="headingOne" data-bs-parent="#accordionExample">
<div class="accordion-body">
<strong>First item content.</strong> Shown by default.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingTwo">
<button class="accordion-button collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#collapseTwo"
aria-expanded="false" aria-controls="collapseTwo">
Accordion Item #2
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse"
aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
<div class="accordion-body">
<strong>Second item content.</strong>
</div>
</div>
</div>
</div>Accordion Flush & Variants
accordion-flush removes the outer borders and background — for integration into cards or containers without double borders. Without data-bs-parent on the collapse items, multiple sections can stay open simultaneously (always-open mode). With data-bs-parent, opening one closes the others (standard accordion). Choose based on UX: exclusive (one open) for FAQs where users read one at a time; multi-open for navigation or settings where users may compare sections.
<!-- flush (no borders, no background) -->
<div class="accordion accordion-flush" id="accordionFlush">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" data-bs-toggle="collapse"
data-bs-target="#flush1">
Item #1
</button>
</h2>
<div id="flush1" class="accordion-collapse collapse">
<div class="accordion-body">Flush content</div>
</div>
</div>
</div>
<!-- flush removes outer borders and background -->
<!-- always-open (no exclusive behavior) -->
<div class="accordion" id="accordionAlwaysOpen">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" data-bs-toggle="collapse"
data-bs-target="#open1">
Item #1
</button>
</h2>
<div id="open1" class="accordion-collapse collapse show">
<!-- no data-bs-parent = stays open when others open -->
<div class="accordion-body">Content</div>
</div>
</div>
</div>Collapse via JavaScript
Control collapse via JS: new bootstrap.Collapse(el, options). toggle: false prevents auto-toggling on init. parent: selector creates accordion behavior (closing siblings). Methods: show(), hide(), toggle(), dispose(). Events: show/shown/hide/hidden.bs.collapse — use shown/hidden (past tense) for post-animation actions like focusing an input or updating aria. This is useful for programmatic control, like expanding a section based on URL hash or form state.
<div class="collapse" id="jsCollapse">
<div class="card card-body">Content</div>
</div>
<script>
const collapseEl = document.getElementById('jsCollapse')
const collapse = new bootstrap.Collapse(collapseEl, {
toggle: false, // don't toggle on init
parent: null // or selector for accordion behavior
})
// methods
collapse.show()
collapse.hide()
collapse.toggle()
// dispose (removes instance)
collapse.dispose()
// events
collapseEl.addEventListener('show.bs.collapse', () => {
console.log('about to show')
})
collapseEl.addEventListener('shown.bs.collapse', () => {
console.log('fully shown')
})
collapseEl.addEventListener('hide.bs.collapse', () => {
console.log('about to hide')
})
collapseEl.addEventListener('hidden.bs.collapse', () => {
console.log('fully hidden')
})
</script>Multi-Target Collapse
data-bs-target accepts CSS selectors, so one trigger can toggle multiple elements — use a shared class (.multi-collapse) to target them all. Conversely, multiple triggers can control the same target (each has data-bs-target='#shared'). This is useful for 'Show all' / 'Hide all' buttons that control multiple sections, or for responsive layouts where different buttons control the same panel from different locations. The selector approach is very flexible.
<!-- one trigger toggles multiple targets -->
<p>
<button class="btn btn-primary" data-bs-toggle="collapse"
data-bs-target=".multi-collapse" aria-expanded="false">
Toggle both
</button>
</p>
<div class="row">
<div class="col">
<div class="collapse multi-collapse" id="first">
<div class="card card-body">First block</div>
</div>
</div>
<div class="col">
<div class="collapse multi-collapse" id="second">
<div class="card card-body">Second block</div>
</div>
</div>
</div>
<!-- multiple triggers control one target -->
<button class="btn btn-primary" data-bs-toggle="collapse"
data-bs-target="#shared">Show A</button>
<button class="btn btn-secondary" data-bs-toggle="collapse"
data-bs-target="#shared">Show B</button>
<div class="collapse" id="shared">
<div class="card card-body">Shared content</div>
</div>Carousel
Basic Carousel
Carousels cycle through content (images, text, or custom). carousel > carousel-inner > carousel-item structure. The first item needs 'active' class. carousel-control-prev/next with data-bs-slide='prev'/'next' provide navigation (data-bs-target must point to the carousel's id). d-block w-100 on images makes them display correctly. data-bs-ride='carousel' enables auto-play on load. visually-hidden text on controls is for screen reader accessibility.
<div id="carouselExample" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active">
<img src="slide1.jpg" class="d-block w-100" alt="Slide 1">
</div>
<div class="carousel-item">
<img src="slide2.jpg" class="d-block w-100" alt="Slide 2">
</div>
<div class="carousel-item">
<img src="slide3.jpg" class="d-block w-100" alt="Slide 3">
</div>
</div>
<!-- previous/next controls -->
<button class="carousel-control-prev" type="button"
data-bs-target="#carouselExample" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button"
data-bs-target="#carouselExample" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>Carousel with Indicators
carousel-indicators adds clickable dots at the bottom — one per slide. data-bs-slide-to='0' jumps to that slide index. The active indicator gets 'active' class and aria-current='true'. Indicators let users jump directly to a slide without cycling. Always include indicators for carousels with more than 2-3 slides — they provide navigation context (which slide am I on, how many total). Pair with prev/next controls for full navigation.
<div id="carouselIndicators" class="carousel slide" data-bs-ride="carousel">
<!-- indicators (dots) -->
<div class="carousel-indicators">
<button type="button" data-bs-target="#carouselIndicators"
data-bs-slide-to="0" class="active" aria-current="true"
aria-label="Slide 1"></button>
<button type="button" data-bs-target="#carouselIndicators"
data-bs-slide-to="1" aria-label="Slide 2"></button>
<button type="button" data-bs-target="#carouselIndicators"
data-bs-slide-to="2" aria-label="Slide 3"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active">
<img src="1.jpg" class="d-block w-100" alt="...">
</div>
<div class="carousel-item">
<img src="2.jpg" class="d-block w-100" alt="...">
</div>
</div>
<!-- controls (same as basic) -->
<button class="carousel-control-prev" data-bs-target="#carouselIndicators"
data-bs-slide="prev">...</button>
<button class="carousel-control-next" data-bs-target="#carouselIndicators"
data-bs-slide="next">...</button>
</div>Carousel with Captions
carousel-caption overlays text on slides — great for hero sections. d-none d-md-block hides captions on mobile (where they'd overlap small images) and shows on md+. Captions can include headings, paragraphs, and buttons. For readability on images, add a gradient overlay under the caption (similar to the image overlay pattern). Carousel captions are the standard way to make image carousels into marketing hero sections with CTAs.
<div id="carouselCaptions" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-indicators">
<button data-bs-target="#carouselCaptions" data-bs-slide-to="0"
class="active" aria-current="true"></button>
<button data-bs-target="#carouselCaptions" data-bs-slide-to="1"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active">
<img src="hero1.jpg" class="d-block w-100" alt="...">
<div class="carousel-caption d-none d-md-block">
<h5>First slide label</h5>
<p>Some representative placeholder content.</p>
<a href="#" class="btn btn-primary">Learn more</a>
</div>
</div>
<div class="carousel-item">
<img src="hero2.jpg" class="d-block w-100" alt="...">
<div class="carousel-caption d-none d-md-block">
<h5>Second slide label</h5>
<p>Content for second slide.</p>
</div>
</div>
</div>
</div>Carousel Options & Crossfade
carousel-fade replaces the slide transition with a crossfade. carousel-dark uses dark controls/indicators (for light images). Options: interval (ms between auto-slides), pause ('hover' pauses on mouseover), wrap (loop), touch (swipe support). JS methods: cycle(), pause(), next(), prev(), to(n). Events: slide.bs.carousel (firing) and slid.bs.carousel (done) provide the from/to indices for custom animations or analytics.
<!-- crossfade transition -->
<div id="carouselFade" class="carousel slide carousel-fade" data-bs-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active">
<img src="1.jpg" class="d-block w-100" alt="...">
</div>
<div class="carousel-item">
<img src="2.jpg" class="d-block w-100" alt="...">
</div>
</div>
</div>
<!-- dark variant (for light images) -->
<div class="carousel carousel-dark slide" data-bs-ride="carousel">
<!-- controls and indicators are dark -->
</div>
<!-- via JavaScript with options -->
<script>
const carousel = new bootstrap.Carousel(document.getElementById('myCarousel'), {
interval: 5000, // auto-play every 5s
keyboard: true, // arrow keys navigate
pause: 'hover', // pause on mouse hover
ride: 'carousel', // auto-start
wrap: true, // loop back to first slide
touch: true // swipe on touch devices
})
carousel.cycle() // start auto-play
carousel.pause() // pause
carousel.next() // next slide
carousel.prev() // previous slide
carousel.to(2) // jump to slide 2
</script>Carousel Touch & Responsive
Touch swipe is enabled by default (data-bs-touch='true'). For responsive carousels with different images per breakpoint, stack two imgs with d-block d-md-none (mobile) and d-none d-md-block (desktop) in the same carousel-item — only one shows at a time. data-bs-ride='false' disables auto-play (manual navigation only). data-bs-pause='hover' pauses on mouseover — consider this for content users need to read. Always test carousels on touch devices for swipe behavior.
<!-- touch swipe enabled (default) -->
<div id="carouselTouch" class="carousel slide" data-bs-ride="carousel"
data-bs-touch="true" data-bs-interval="5000">
<div class="carousel-inner">
<div class="carousel-item active">
<img src="1.jpg" class="d-block w-100" alt="...">
</div>
<div class="carousel-item">
<img src="2.jpg" class="d-block w-100" alt="...">
</div>
</div>
</div>
<!-- different image sizes per breakpoint -->
<div class="carousel slide" data-bs-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active">
<!-- mobile image -->
<img src="mobile.jpg"
class="d-block w-100 d-md-none" alt="...">
<!-- desktop image -->
<img src="desktop.jpg"
class="d-none d-md-block w-100" alt="...">
</div>
</div>
</div>
<!-- disable auto-play -->
<div class="carousel slide" data-bs-ride="false">
<!-- only moves via controls -->
</div>
<!-- pause on hover -->
<div class="carousel slide" data-bs-ride="carousel" data-bs-pause="hover">
<!-- pauses when mouse is over -->
</div>Icons & Accessibility
Bootstrap Icons
Bootstrap Icons is a separate icon library (not included in the CSS). Include the CSS, then use <i class='bi bi-{name}'></i>. Size icons with fs-* (font-size) utilities. Color them with text-* classes. Icons in buttons improve UX with visual cues. Icon-only buttons MUST have aria-label for screen readers — without it, the button has no accessible name. Browse icons at icons.getbootstrap.com. Icons are SVG fonts, so they scale crisply at any size.
<!-- Bootstrap Icons (separate from CSS framework) -->
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
<!-- usage: <i class="bi bi-{name}"></i> -->
<i class="bi bi-alarm"></i>
<i class="bi bi-arrow-right"></i>
<i class="bi bi-check-circle"></i>
<i class="bi bi-github"></i>
<!-- sizing with utility classes -->
<i class="bi bi-star fs-1"></i> <!-- font-size 1 -->
<i class="bi bi-star fs-3"></i>
<i class="bi bi-star fs-6"></i>
<!-- colored icons -->
<i class="bi bi-exclamation-triangle text-warning fs-3"></i>
<i class="bi bi-check-circle text-success fs-3"></i>
<!-- icon in a button -->
<button class="btn btn-primary">
<i class="bi bi-download"></i> Download
</button>
<!-- icon-only button (needs aria-label) -->
<button class="btn btn-link" aria-label="Search">
<i class="bi bi-search"></i>
</button>Accessibility: ARIA & Roles
Bootstrap components include ARIA attributes, but you must add them for custom controls. aria-label provides names for icon-only buttons. aria-labelledby references another element's id as a label. aria-describedby points to help text. aria-expanded reflects toggle state (true/false). aria-current='page' marks the current nav item. Use semantic HTML (nav, main, aside, header, footer) which have implicit ARIA roles — better than adding role attributes to divs.
<!-- landmark roles (use semantic HTML) -->
<nav role="navigation">...</nav>
<main role="main">...</main>
<aside role="complementary">...</aside>
<footer role="contentinfo">...</footer>
<!-- aria-label for unlabeled controls -->
<button aria-label="Close" class="btn-close"></button>
<button aria-label="Search" class="btn"><i class="bi bi-search"></i></button>
<!-- aria-labelledby (reference another element) -->
<div role="dialog" aria-labelledby="modalTitle">
<h2 id="modalTitle">Modal Heading</h2>
</div>
<!-- aria-describedby (descriptive text) -->
<input aria-describedby="emailHelp" id="email">
<small id="emailHelp">We'll never share your email.</small>
<!-- aria-expanded for toggles -->
<button aria-expanded="false" data-bs-toggle="collapse">
Toggle
</button>
<!-- aria-current for current page -->
<a aria-current="page" class="nav-link active">Home</a>Visually Hidden & Screen Reader
visually-hidden hides content visually but keeps it for screen readers — use for labels on icon-only buttons or extra context. visually-hidden-focusable is hidden until keyboard focus (perfect for 'Skip to main content' links that appear when Tabbed to). aria-hidden='true' does the opposite: visible but not announced (for decorative elements). Always provide a skip link as the first focusable element for keyboard accessibility. These are essential for WCAG compliance.
<!-- visually hidden (hidden visually, read by screen readers) -->
<span class="visually-hidden">This text is read by screen readers</span>
<!-- for icon-only buttons -->
<button class="btn btn-primary" aria-label="Search">
<i class="bi bi-search"></i>
<span class="visually-hidden">Search</span>
</button>
<!-- skip link (keyboard users skip to content) -->
<a href="#main-content" class="visually-hidden-focusable">
Skip to main content
</a>
<!-- visually-hidden-focusable: hidden until focused -->
<!-- useful for skip links that appear on Tab -->
<!-- main content target -->
<main id="main-content">
<!-- skip link jumps here -->
</main>
<!-- hide from screen readers only (still visible) -->
<div aria-hidden="true">
Decorative content (not announced)
</div>Reduced Motion & Dark Mode
Dark mode: set data-bs-theme='dark' on <html> — Bootstrap swaps CSS variables. Toggle via JS by changing the attribute. Respect system preference with matchMedia('(prefers-color-scheme: dark)'). For reduced motion, Bootstrap automatically disables most animations when the user has prefers-reduced-motion: reduce set. You can add more rules to further reduce motion. Store the user's theme preference in localStorage. These features make your site accessible and comfortable for all users.
<!-- dark mode via data attribute -->
<html data-bs-theme="dark">
<!-- entire page in dark mode -->
</html>
<!-- toggle dark mode via JS -->
<button id="themeToggle">Toggle theme</button>
<script>
const html = document.documentElement
const current = html.getAttribute('data-bs-theme')
html.setAttribute('data-bs-theme', current === 'dark' ? 'light' : 'dark')
</script>
<!-- respect system preference -->
<script>
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-bs-theme', 'dark')
}
</script>
<!-- reduced motion: Bootstrap respects this automatically -->
@media (prefers-reduced-motion: reduce) {
/* Bootstrap disables transitions and animations */
/* but you can add more */
.carousel {
transition: none;
}
}Focus Management & Keyboard
Never remove focus outlines (outline: none) without providing an alternative — keyboard users need them. Bootstrap's focus-visible shows the ring only for keyboard focus, not mouse. For modals, move focus into the modal on show and return to the trigger on hide. Custom widgets (tabs, menus) need arrow key navigation — implement keydown handlers for Arrow keys, Enter, Space, and Escape. Proper tabindex management (0 for focusable, -1 for programmatically focusable) ensures logical tab order. This is essential for keyboard-only users.
<!-- visible focus (don't remove outlines!) -->
<!-- Bootstrap provides focus-visible styling -->
<button class="btn btn-primary">Has visible focus ring</button>
<!-- skip link (first focusable element) -->
<body>
<a href="#main" class="visually-hidden-focusable">Skip to content</a>
<nav>...</nav>
<main id="main">...</main>
</body>
<!-- focus management for modals -->
<script>
const modal = document.getElementById('myModal')
modal.addEventListener('shown.bs.modal', () => {
// focus first input in modal
modal.querySelector('input')?.focus()
})
modal.addEventListener('hidden.bs.modal', () => {
// return focus to trigger
document.getElementById('openModalBtn').focus()
})
</script>
<!-- keyboard navigation for custom widgets -->
<div role="tablist">
<button role="tab" tabindex="0">Tab 1</button>
<button role="tab" tabindex="-1">Tab 2</button>
</div>
<script>
// arrow key navigation
document.querySelectorAll('[role="tab"]').forEach((tab, i, tabs) => {
tab.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') tabs[(i + 1) % tabs.length].focus()
if (e.key === 'ArrowLeft') tabs[(i - 1 + tabs.length) % tabs.length].focus()
})
})
</script>List Group
Basic List Group
list-group is a flexible component for displaying a series of content. list-group-item styles each entry. active marks the current item; disabled greys it out (use aria-disabled='true' for accessibility). List groups are more semantic than styled divs for lists of related content like dashboards, settings, or inbox items. Use <ul>/<li> for navigation lists, or <div> for non-list content.
<!-- basic list group -->
<ul class="list-group">
<li class="list-group-item">An item</li>
<li class="list-group-item">A second item</li>
<li class="list-group-item">A third item</li>
<li class="list-group-item">A fourth item</li>
<li class="list-group-item">A disabled item</li>
</ul>
<!-- with active item -->
<ul class="list-group">
<li class="list-group-item active" aria-current="true">Active item</li>
<li class="list-group-item">Regular item</li>
</ul>
<!-- disabled item -->
<ul class="list-group">
<li class="list-group-item disabled" aria-disabled="true">Disabled</li>
</ul>List Group with Links & Buttons
For actionable list items (clickable), use list-group-item-action on <a> or <button> elements. This adds hover and focus states. Use <a> for navigation (with href) and <button type='button'> for actions. Disable links with the .disabled class (and aria-disabled); disable buttons with the disabled attribute. Avoid using <li> for actionable items — use <div> as the list-group wrapper instead, since <a>/<button> inside <ul> is less semantic.
<!-- list group with links (actionable items) -->
<div class="list-group">
<a href="#" class="list-group-item list-group-item-action active"
aria-current="true">
Active link item
</a>
<a href="#" class="list-group-item list-group-item-action">Link item</a>
<a href="#" class="list-group-item list-group-item-action">Another link</a>
<a class="list-group-item list-group-item-action disabled">Disabled link</a>
</div>
<!-- with buttons instead of links -->
<div class="list-group">
<button type="button" class="list-group-item list-group-item-action active">
Button item
</button>
<button type="button" class="list-group-item list-group-item-action">
Another button
</button>
</div>
<!-- note: use list-group-item-action for hover/focus states -->List Group Flush & Numbered
list-group-flush removes borders and rounded corners — for embedding inside cards or containers without double borders. list-group-numbered (on <ol>) adds automatic numbering via CSS counters. list-group-horizontal makes items sit side by side; responsive variants (list-group-horizontal-md) switch to horizontal at a breakpoint. Horizontal lists are useful for small navigation or filter bars. Numbered lists are great for step-by-step instructions or rankings.
<!-- flush (no borders, no background, edge-to-edge) -->
<ul class="list-group list-group-flush">
<li class="list-group-item">Flush item 1</li>
<li class="list-group-item">Flush item 2</li>
<li class="list-group-item">Flush item 3</li>
</ul>
<!-- removes outer borders and rounded corners -->
<!-- numbered list group (ordered) -->
<ol class="list-group list-group-numbered">
<li class="list-group-item">First item</li>
<li class="list-group-item">Second item</li>
<li class="list-group-item">Third item</li>
</ol>
<!-- CSS counters add the numbers automatically -->
<!-- horizontal list group -->
<ul class="list-group list-group-horizontal">
<li class="list-group-item">Item 1</li>
<li class="list-group-item">Item 2</li>
<li class="list-group-item">Item 3</li>
</ul>
<!-- responsive horizontal -->
<ul class="list-group list-group-horizontal-md">
<!-- horizontal at md+, vertical below -->
</ul>List Group Contextual Colors & Badges
Contextual classes (list-group-item-{color}) tint items for status indication — use meaningfully (success=done, danger=error). For badges inside list items, use d-flex justify-content-between align-items-center to push the badge to the right and vertically center it. rounded-pill badges look modern for counts. This pattern is the classic inbox/notification list: label on left, count on right. The flex utilities handle alignment without custom CSS.
<!-- contextual color items -->
<ul class="list-group">
<li class="list-group-item list-group-item-primary">Primary item</li>
<li class="list-group-item list-group-item-secondary">Secondary</li>
<li class="list-group-item list-group-item-success">Success</li>
<li class="list-group-item list-group-item-danger">Danger</li>
<li class="list-group-item list-group-item-warning">Warning</li>
<li class="list-group-item list-group-item-info">Info</li>
<li class="list-group-item list-group-item-light">Light</li>
<li class="list-group-item list-group-item-dark">Dark</li>
</ul>
<!-- with badges (counts aligned right) -->
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between align-items-center">
Messages
<span class="badge bg-primary rounded-pill">14</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
Notifications
<span class="badge bg-danger rounded-pill">3</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
Spam
<span class="badge bg-warning text-dark rounded-pill">1</span>
</li>
</ul>List Group Custom Content
List items can contain rich content: headings (fw-bold), descriptions (text-muted), metadata (small), and badges. Use d-flex justify-content-between align-items-start to lay out content and badge. me-auto pushes the badge to the right. For checkboxes/radios in list items, use form-check-input with a matching form-check-label — this creates selectable lists. Common use cases: email lists, settings panels, and search results where each item has a title, description, and status.
<!-- rich content list item -->
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">Subheading</div>
<p class="mb-0 text-muted">Descriptive text for the list item.</p>
<small>Additional metadata</small>
</div>
<span class="badge bg-primary rounded-pill">New</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="fw-bold">Another item</div>
<p class="mb-0">More content here.</p>
</div>
<span class="badge bg-secondary rounded-pill">3</span>
</li>
</ul>
<!-- list group with checkbox/radio -->
<ul class="list-group">
<li class="list-group-item">
<input class="form-check-input me-1" type="checkbox" id="check1">
<label class="form-check-label" for="check1">Checkbox item</label>
</li>
<li class="list-group-item">
<input class="form-check-input me-1" type="radio" name="radio" id="radio1">
<label class="form-check-label" for="radio1">Radio item</label>
</li>
</ul>Verwandte Bootstrap-Snippets
Copy-paste ready code for common tasks.
Grid System
Lay out pages with the 12-column responsive grid.
Responsive Utilities
Show, hide, and reorder elements per breakpoint.
Navbar
Build a responsive collapsing navbar with brand and links.
Cards
Compose a card with image, body, list, and footer.
Modal
Trigger and structure a Bootstrap modal dialog.
Forms & Validation
Build a responsive form with validation feedback.
Buttons
Use button variants, sizes, states, and groups.
Alerts
Display contextual messages with dismissible alerts.
Was this helpful?