Bootstrap Migration Guide
Mappings are for current Bootstrap. Class names from Bootstrap 3 and 4 are not listed. If you still have v4 leftovers (btn-block, badge-primary, text-left, form-group, sr-only), translate those to current Bootstrap first, then use this sheet.
Path: swap Bootstrap CSS for @airframeui/core, replace classes with the tables below, then drop Bootstrap JS (bootstrap.bundle, data-bs-*). Airframe patterns are CSS and native HTML (<dialog>, <details>). Catch leftovers with the ESLint plugin.
Breakpoints
Section titled “Breakpoints”Infix names look the same (sm, md, lg, xl). Pixel values are not.
| Bootstrap | Min width | Airframe | Min width |
|---|---|---|---|
| (none / xs) | 0 | (none) | 0 |
sm | 576px | @sm | 640px |
md | 768px | @md | 768px |
lg | 992px | @lg | 1024px |
xl | 1200px | @xl | 1280px |
xxl | 1400px | @2xl | 1536px |
col-md-6 → af-col-span-6@md is the class-name mapping. Recheck layout at lg / xl / xxl — those infixes do not fire at the same width.
Patterns
Section titled “Patterns”Buttons
Section titled “Buttons”| Bootstrap | Airframe | Notes |
|---|---|---|
btn | af-btn | Base button |
btn btn-primary | af-btn | Primary is the default |
btn btn-secondary | af-btn af-is-secondary | Secondary |
btn btn-success | af-btn af-is-success | Success |
btn btn-danger | af-btn af-is-danger | Danger |
btn btn-warning | af-btn af-is-warning | Warning |
btn btn-info | af-btn af-is-info | Info |
btn btn-outline-primary | af-btn af-is-outline | Outline (primary is default) |
btn btn-outline-secondary | af-btn af-is-outline af-is-secondary | Outline + intent |
btn btn-link | af-btn af-is-link | Looks like a link; keep <button> |
btn btn-sm | af-btn af-is-sm | Small |
btn btn-lg | af-btn af-is-lg | Large |
btn w-100 | af-btn af-w-full | Full-width. There is no af-is-block |
btn-group | af-btn-group | Joined buttons |
btn-close | af-btn af-btn-icon + aria-label | No Bootstrap close glyph |
Pattern tokens (overrides)
Section titled “Pattern tokens (overrides)”Use CSS variables to tweak sizing without new classes:
<style>
:root {
--af-control-min-height: 3rem;
--af-btn-gap: 0.75rem;
--af-input-placeholder-color: rgba(0, 0, 0, 0.45);
}
</style>
<button class="af-btn">Primary</button>
<input class="af-input" placeholder="Custom input" />| Bootstrap | Airframe | Notes |
|---|---|---|
card | af-card | Base card |
card-body | af-card__body | Optional — Airframe also allows a <p class="af-card__body"> with no wrapper |
card-header | af-card__header | Card header |
card-footer | af-card__footer | Card footer |
card-title | af-card__title | Card title |
card-text | (plain <p>) | No extra class needed |
card-subtitle | (plain heading) | No extra class needed |
Alerts
Section titled “Alerts”| Bootstrap | Airframe | Notes |
|---|---|---|
alert | af-alert | Base alert |
alert alert-primary | af-alert af-is-primary | Primary alert |
alert alert-success | af-alert af-is-success | Success alert |
alert alert-danger | af-alert af-is-danger | Danger alert |
alert alert-warning | af-alert af-is-warning | Warning alert |
alert alert-info | af-alert af-is-info | Info alert |
Dismissible alerts (alert-dismissible + btn-close) have no Bootstrap JS equivalent. Put a close control in the alert and hide it yourself, or use a toast.
Badges
Section titled “Badges”Bootstrap dropped badge-* color classes. Use text-bg-* (or bg-*).
| Bootstrap | Airframe | Notes |
|---|---|---|
badge | af-badge | Base badge |
badge text-bg-primary | af-badge | Primary/default |
badge text-bg-secondary | af-badge af-is-secondary | Secondary |
badge text-bg-success | af-badge af-is-success | Success |
badge text-bg-danger | af-badge af-is-danger | Danger |
badge text-bg-warning | af-badge af-is-warning | Warning |
badge text-bg-info | af-badge af-is-info | Info |
badge rounded-pill | af-badge | Airframe badges are already rounded |
af-pill is a different pattern (announcement / promo), not a round badge.
| Bootstrap | Airframe | Notes |
|---|---|---|
form-control | af-input | Text, email, password, etc. |
form-select | af-select or form.af-form select | Select (class or af-form wrapper) |
form-control on <textarea> | af-textarea | Textarea |
form-label | af-label or af-field__label | Label |
form-text | af-field__help | Help text |
mb-3 around a field | af-field | Field stack with spacing |
form-check + form-check-input (checkbox) | label wrapping af-checkbox | Markup change, not a class rename |
form-check + form-check-input (radio) | label wrapping af-radio | Same wrapping pattern |
form-check-label | <span> inside the label | Label text |
form-check form-switch | <input type="checkbox" role="switch"> | See Switch |
form-range | af-range | Range |
input-group | af-input-group | One field; prefix/suffix sit inside |
input-group-text | af-input-group-addon | Decorative prefix/suffix, not a boxed addon |
Appended btn in the group | af-input-group-action | Reveal/clear/copy inside the field. A submit goes beside the field in af-inline, not joined on. |
is-invalid | af-is-error + aria-invalid="true" | Wire the message with aria-describedby |
invalid-feedback | af-field__error | Validation |
form-floating | af-field | No floating-label pattern |
row g-* + col-* form layout | af-field-inline or af-grid | Form layout |
Checkbox markup (wrapping is the mapping, not form-check-input → af-checkbox on the same structure):
<!-- Bootstrap -->
<div class="form-check">
<input class="form-check-input" type="checkbox" id="notify">
<label class="form-check-label" for="notify">Notify me</label>
</div>
<!-- Airframe -->
<label>
<input type="checkbox" class="af-checkbox" />
<span>Notify me</span>
</label>For classless controls, wrap in form.af-form. Selects need either .af-select or that wrapper.
JS components → Airframe patterns
Section titled “JS components → Airframe patterns”Bootstrap components that ship with JavaScript. Drop data-bs-toggle / data-bs-target / bootstrap.bundle.
| Bootstrap | Airframe | Notes |
|---|---|---|
| Accordion | Accordion (<details>) | No Collapse plugin |
| Modal | Dialog (<dialog>) | showModal() / close() |
| Offcanvas | Drawer (<dialog>) | |
| Dropdown | Dropdown menu | Native popover; arrow keys are yours |
| Navbar collapse | Nav collapse | You own toggle JS |
| Navs / tabs | Tabs or Nav pills | CSS-only tabs, or JS you control |
| Toast | Toast | CSS only; you own show/hide |
| Tooltip | Tooltip | af-tooltip + aria-label; no Popper |
| Popover | Dialog or tooltip | No 1:1 |
| Spinner | Spinner | |
| Placeholder | Skeleton | |
| Progress | Progress | Prefer <progress> |
| Pagination | Pagination | |
| Breadcrumb | Breadcrumbs | |
| List group | List group | |
| Navbar | Navbar | |
| Table | Table (af-table, af-table-striped) | |
| Carousel | — | No 1:1 |
| Scrollspy | — | No 1:1 |
Grid System
Section titled “Grid System”Simple Grid (Recommended for Equal Columns)
Section titled “Simple Grid (Recommended for Equal Columns)”For equal-width columns, prefer the simple grid. By default: af-grid adjusts to the number of children. Bootstrap row-cols-* is the closest source pattern.
| Bootstrap | Airframe | Notes |
|---|---|---|
row row-cols-2 | af-grid or af-grid-2 | Auto-adjusts to 2 columns |
row row-cols-3 | af-grid or af-grid-3 | Auto-adjusts to 3 columns |
row row-cols-4 | af-grid or af-grid-4 | Auto-adjusts to 4 columns |
row with 2 equal col-6 | af-grid | Auto-adjusts to 2 columns |
row with 3 equal col-4 | af-grid | Auto-adjusts to 3 columns |
row with 4 equal col-3 | af-grid | Auto-adjusts to 4 columns |
row row-cols-1 row-cols-md-2 | af-grid af-grid-1@sm af-grid-2@md | Responsive equal columns |
row with 5 equal columns | af-grid-5 | 5 columns |
row with 6 equal columns | af-grid-6 | 6 columns |
Example:
<!-- Bootstrap -->
<div class="row row-cols-2">
<div class="col">Item 1</div>
<div class="col">Item 2</div>
</div>
<!-- Airframe (auto-adjusting) -->
<div class="af-grid">
<div>Item 1</div>
<div>Item 2</div>
</div>
<!-- Airframe (explicit - also works) -->
<div class="af-grid-2">
<div>Item 1</div>
<div>Item 2</div>
</div>12-Column Grid (For Bootstrap Migration & Asymmetric Layouts)
Section titled “12-Column Grid (For Bootstrap Migration & Asymmetric Layouts)”Use the 12-column system when migrating Bootstrap markup or when you need asymmetric layouts:
| Bootstrap | Airframe | Notes |
|---|---|---|
container | af-container | Centered, max-width |
container-sm … container-xxl | af-container-sm … af-container-2xl | Always that max-width. Bootstrap’s container-lg is fluid until lg; Airframe is not. Use one class, not af-container af-container-xl |
container-fluid | padded wrapper | Do not use af-container — that one has a max-width. Use af-px-lg (or similar) on a full-width element |
row | af-grid | Auto-detects 12-column when children have af-col-span-* |
col | (no class needed) | Full width is default |
col-6 | af-col-span-6 | Half width (or use af-grid-2 for new code) |
col-4 | af-col-span-4 | One third (or use af-grid-3 for new code) |
col-3 | af-col-span-3 | One quarter (or use af-grid-4 for new code) |
col-8 | af-col-span-8 | Two thirds (asymmetric — use 12-col) |
col-12 | (no class needed) | Full width is default |
Note: Use af-grid with af-col-span-* for a direct Bootstrap port. The grid detects span classes and applies a 12-column layout. For equal-width columns, use af-grid (auto-adjusts) or af-grid-2 through af-grid-6.
Responsive Columns
Section titled “Responsive Columns”| Bootstrap | Airframe | Notes |
|---|---|---|
col-12 col-md-6 (equal) | af-grid af-grid-1@sm af-grid-2@md | Prefer simple grid |
col-12 col-md-6 (keep 12-col) | af-col-span-6@md | Direct migration (full width is default) |
col-12 col-lg-8 | af-col-span-8@lg | Asymmetric (full width is default) |
col-12 col-lg-4 | af-col-span-4@lg | Asymmetric (full width is default) |
col-sm-6 col-md-4 (equal) | af-grid af-grid-2@sm af-grid-3@md | Prefer simple grid |
col-lg-6 | af-col-span-6@lg | Large infix only |
col-xxl-6 | af-col-span-6@2xl | xxl → @2xl |
Grid Gaps
Section titled “Grid Gaps”Bootstrap gutters (g-* on .row) and CSS gap-* utilities share the same 0–5 scale.
| Bootstrap | Airframe | Notes |
|---|---|---|
row g-0 | af-grid af-gap-0 | No gap |
row g-1 | af-grid af-gap-xs | Extra small |
row g-2 | af-grid af-gap-sm | Small |
row g-3 | af-grid | Medium (Airframe default) |
row g-4 | af-grid af-gap-lg | Large |
row g-5 | af-grid af-gap-xl | Extra large |
d-flex gap-2 | af-inline af-gap-sm | Flex gap → recipe + named gap |
gx-* / gy-* | af-gap-* | One gap token; no separate axis classes |
Layout Utilities
Section titled “Layout Utilities”Display
Section titled “Display”| Bootstrap | Airframe | Notes |
|---|---|---|
d-flex | af-inline | Flex display (recipe-first) |
d-flex flex-column | af-stack | Vertical stack |
d-flex flex-row | af-inline | Horizontal inline |
d-flex flex-wrap | af-inline | Wrapping inline |
d-grid | af-grid | Grid display |
d-none | af-hidden | Hidden |
d-block | af-block | Block display |
visually-hidden | af-sr-only | Screen readers |
visually-hidden-focusable | af-sr-only-focusable | Skip links use af-skip |
Inline Layout Patterns
Section titled “Inline Layout Patterns”| Bootstrap | Airframe | Notes |
|---|---|---|
d-flex flex-wrap align-items-center | af-cluster | Default semantic cluster |
d-flex flex-wrap align-items-center (explicit) | af-inline af-items-center | Explicit utilities |
d-flex align-items-center justify-content-between | af-spread | Default semantic spread |
d-flex align-items-center justify-content-between (explicit) | af-inline af-items-center af-justify-between | Explicit utilities |
d-flex align-items-center justify-content-center | af-center | Default semantic center |
d-flex align-items-center justify-content-center (explicit) | af-inline af-items-center af-justify-center | Explicit utilities |
Text Alignment
Section titled “Text Alignment”| Bootstrap | Airframe | Notes |
|---|---|---|
text-center | af-text-center | Center text |
text-start | af-text-start | Start-aligned (LTR/RTL safe) |
text-end | af-text-end | End-aligned (LTR/RTL safe) |
| Bootstrap | Airframe | Notes |
|---|---|---|
w-100 | af-w-full | Full width |
Spacing
Section titled “Spacing”Bootstrap spacing is 0–5. Airframe uses named steps. Logical ms-* / me-* / ps-* / pe-* map to Airframe’s physical af-ml-* / af-mr-* / af-pl-* / af-pr-* in LTR.
Margin
Section titled “Margin”| Bootstrap | Airframe | Notes |
|---|---|---|
m-0 | af-m-0 | No margin |
m-1 | af-m-xs | Extra small |
m-2 | af-m-sm | Small |
m-3 | af-m-md | Medium |
m-4 | af-m-lg | Large |
m-5 | af-m-xl | Extra large |
Padding
Section titled “Padding”| Bootstrap | Airframe | Notes |
|---|---|---|
p-0 | af-p-0 | No padding |
p-1 | af-p-xs | Extra small |
p-2 | af-p-sm | Small |
p-3 | af-p-md | Medium |
p-4 | af-p-lg | Large |
p-5 | af-p-xl | Extra large |
Common Patterns
Section titled “Common Patterns”Card Layout
Section titled “Card Layout”<!-- Bootstrap -->
<div class="card">
<div class="card-header">Header</div>
<div class="card-body">
<h5 class="card-title">Title</h5>
<p class="card-text">Content</p>
</div>
<div class="card-footer">Footer</div>
</div>
<!-- Airframe -->
<div class="af-card">
<div class="af-card__header">Header</div>
<div class="af-card__body">
<h5 class="af-card__title">Title</h5>
<p>Content</p>
</div>
<div class="af-card__footer">Footer</div>
</div>Airframe also allows a flatter card (af-card + af-card__title + af-card__body with no inner wrapper). See Card.
Grid Layout
Section titled “Grid Layout”<!-- Bootstrap -->
<div class="container">
<div class="row g-3">
<div class="col-12 col-lg-8">Main Content</div>
<div class="col-12 col-lg-4">Sidebar</div>
</div>
</div>
<!-- Airframe -->
<div class="af-container">
<div class="af-grid">
<div class="af-col-span-8@lg">Main Content</div>
<div class="af-col-span-4@lg">Sidebar</div>
</div>
</div>Adjacent Buttons
Section titled “Adjacent Buttons”<!-- Bootstrap -->
<div class="d-flex gap-2">
<button class="btn btn-primary">Save</button>
<button class="btn btn-secondary">Cancel</button>
</div>
<!-- Airframe -->
<div class="af-inline af-gap-sm">
<button class="af-btn">Save</button>
<button class="af-btn af-is-secondary">Cancel</button>
</div>Joined controls use btn-group → af-btn-group. See Button group.
Alert with Button
Section titled “Alert with Button”<!-- Bootstrap -->
<div class="alert alert-warning d-flex justify-content-between align-items-center">
<span>Warning message</span>
<button class="btn btn-sm btn-warning">Action</button>
</div>
<!-- Airframe -->
<div class="af-alert af-is-warning af-inline af-justify-between af-items-center">
<span>Warning message</span>
<button class="af-btn af-is-sm af-is-warning">Action</button>
</div>Form Layout
Section titled “Form Layout”<!-- Bootstrap -->
<div class="container">
<div class="row">
<div class="col-12 col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">Form Title</h5>
<form>
<div class="mb-3">
<label class="form-label" for="name">Name</label>
<input type="text" id="name" class="form-control">
</div>
<div class="mb-3">
<label class="form-label" for="country">Country</label>
<select id="country" class="form-select">
<option>Choose...</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Airframe -->
<div class="af-container">
<div class="af-grid">
<div class="af-col-span-6@md">
<div class="af-card">
<div class="af-card__body">
<h5 class="af-card__title">Form Title</h5>
<form class="af-form">
<div class="af-field">
<label class="af-field__label" for="name">Name</label>
<input type="text" id="name" class="af-input">
</div>
<div class="af-field">
<label class="af-field__label" for="country">Country</label>
<select id="country" class="af-select">
<option>Choose...</option>
</select>
</div>
<button type="submit" class="af-btn">Submit</button>
</form>
</div>
</div>
</div>
</div>
</div>See also: Tailwind cheat sheet | Coming from Bootstrap or Tailwind