Skip to content

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.

Infix names look the same (sm, md, lg, xl). Pixel values are not.

BootstrapMin widthAirframeMin width
(none / xs)0(none)0
sm576px@sm640px
md768px@md768px
lg992px@lg1024px
xl1200px@xl1280px
xxl1400px@2xl1536px

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.

BootstrapAirframeNotes
btnaf-btnBase button
btn btn-primaryaf-btnPrimary is the default
btn btn-secondaryaf-btn af-is-secondarySecondary
btn btn-successaf-btn af-is-successSuccess
btn btn-dangeraf-btn af-is-dangerDanger
btn btn-warningaf-btn af-is-warningWarning
btn btn-infoaf-btn af-is-infoInfo
btn btn-outline-primaryaf-btn af-is-outlineOutline (primary is default)
btn btn-outline-secondaryaf-btn af-is-outline af-is-secondaryOutline + intent
btn btn-linkaf-btn af-is-linkLooks like a link; keep <button>
btn btn-smaf-btn af-is-smSmall
btn btn-lgaf-btn af-is-lgLarge
btn w-100af-btn af-w-fullFull-width. There is no af-is-block
btn-groupaf-btn-groupJoined buttons
btn-closeaf-btn af-btn-icon + aria-labelNo Bootstrap close glyph

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" />
BootstrapAirframeNotes
cardaf-cardBase card
card-bodyaf-card__bodyOptional — Airframe also allows a <p class="af-card__body"> with no wrapper
card-headeraf-card__headerCard header
card-footeraf-card__footerCard footer
card-titleaf-card__titleCard title
card-text(plain <p>)No extra class needed
card-subtitle(plain heading)No extra class needed
BootstrapAirframeNotes
alertaf-alertBase alert
alert alert-primaryaf-alert af-is-primaryPrimary alert
alert alert-successaf-alert af-is-successSuccess alert
alert alert-dangeraf-alert af-is-dangerDanger alert
alert alert-warningaf-alert af-is-warningWarning alert
alert alert-infoaf-alert af-is-infoInfo 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.

Bootstrap dropped badge-* color classes. Use text-bg-* (or bg-*).

BootstrapAirframeNotes
badgeaf-badgeBase badge
badge text-bg-primaryaf-badgePrimary/default
badge text-bg-secondaryaf-badge af-is-secondarySecondary
badge text-bg-successaf-badge af-is-successSuccess
badge text-bg-dangeraf-badge af-is-dangerDanger
badge text-bg-warningaf-badge af-is-warningWarning
badge text-bg-infoaf-badge af-is-infoInfo
badge rounded-pillaf-badgeAirframe badges are already rounded

af-pill is a different pattern (announcement / promo), not a round badge.

BootstrapAirframeNotes
form-controlaf-inputText, email, password, etc.
form-selectaf-select or form.af-form selectSelect (class or af-form wrapper)
form-control on <textarea>af-textareaTextarea
form-labelaf-label or af-field__labelLabel
form-textaf-field__helpHelp text
mb-3 around a fieldaf-fieldField stack with spacing
form-check + form-check-input (checkbox)label wrapping af-checkboxMarkup change, not a class rename
form-check + form-check-input (radio)label wrapping af-radioSame wrapping pattern
form-check-label<span> inside the labelLabel text
form-check form-switch<input type="checkbox" role="switch">See Switch
form-rangeaf-rangeRange
input-groupaf-input-groupOne field; prefix/suffix sit inside
input-group-textaf-input-group-addonDecorative prefix/suffix, not a boxed addon
Appended btn in the groupaf-input-group-actionReveal/clear/copy inside the field. A submit goes beside the field in af-inline, not joined on.
is-invalidaf-is-error + aria-invalid="true"Wire the message with aria-describedby
invalid-feedbackaf-field__errorValidation
form-floatingaf-fieldNo floating-label pattern
row g-* + col-* form layoutaf-field-inline or af-gridForm 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.

Bootstrap components that ship with JavaScript. Drop data-bs-toggle / data-bs-target / bootstrap.bundle.

BootstrapAirframeNotes
AccordionAccordion (<details>)No Collapse plugin
ModalDialog (<dialog>)showModal() / close()
OffcanvasDrawer (<dialog>)
DropdownDropdown menuNative popover; arrow keys are yours
Navbar collapseNav collapseYou own toggle JS
Navs / tabsTabs or Nav pillsCSS-only tabs, or JS you control
ToastToastCSS only; you own show/hide
TooltipTooltipaf-tooltip + aria-label; no Popper
PopoverDialog or tooltipNo 1:1
SpinnerSpinner
PlaceholderSkeleton
ProgressProgressPrefer <progress>
PaginationPagination
BreadcrumbBreadcrumbs
List groupList group
NavbarNavbar
TableTable (af-table, af-table-striped)
Carousel—No 1:1
Scrollspy—No 1:1
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.

BootstrapAirframeNotes
row row-cols-2af-grid or af-grid-2Auto-adjusts to 2 columns
row row-cols-3af-grid or af-grid-3Auto-adjusts to 3 columns
row row-cols-4af-grid or af-grid-4Auto-adjusts to 4 columns
row with 2 equal col-6af-gridAuto-adjusts to 2 columns
row with 3 equal col-4af-gridAuto-adjusts to 3 columns
row with 4 equal col-3af-gridAuto-adjusts to 4 columns
row row-cols-1 row-cols-md-2af-grid af-grid-1@sm af-grid-2@mdResponsive equal columns
row with 5 equal columnsaf-grid-55 columns
row with 6 equal columnsaf-grid-66 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:

BootstrapAirframeNotes
containeraf-containerCentered, max-width
container-sm … container-xxlaf-container-sm … af-container-2xlAlways that max-width. Bootstrap’s container-lg is fluid until lg; Airframe is not. Use one class, not af-container af-container-xl
container-fluidpadded wrapperDo not use af-container — that one has a max-width. Use af-px-lg (or similar) on a full-width element
rowaf-gridAuto-detects 12-column when children have af-col-span-*
col(no class needed)Full width is default
col-6af-col-span-6Half width (or use af-grid-2 for new code)
col-4af-col-span-4One third (or use af-grid-3 for new code)
col-3af-col-span-3One quarter (or use af-grid-4 for new code)
col-8af-col-span-8Two 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.

BootstrapAirframeNotes
col-12 col-md-6 (equal)af-grid af-grid-1@sm af-grid-2@mdPrefer simple grid
col-12 col-md-6 (keep 12-col)af-col-span-6@mdDirect migration (full width is default)
col-12 col-lg-8af-col-span-8@lgAsymmetric (full width is default)
col-12 col-lg-4af-col-span-4@lgAsymmetric (full width is default)
col-sm-6 col-md-4 (equal)af-grid af-grid-2@sm af-grid-3@mdPrefer simple grid
col-lg-6af-col-span-6@lgLarge infix only
col-xxl-6af-col-span-6@2xlxxl → @2xl

Bootstrap gutters (g-* on .row) and CSS gap-* utilities share the same 0–5 scale.

BootstrapAirframeNotes
row g-0af-grid af-gap-0No gap
row g-1af-grid af-gap-xsExtra small
row g-2af-grid af-gap-smSmall
row g-3af-gridMedium (Airframe default)
row g-4af-grid af-gap-lgLarge
row g-5af-grid af-gap-xlExtra large
d-flex gap-2af-inline af-gap-smFlex gap → recipe + named gap
gx-* / gy-*af-gap-*One gap token; no separate axis classes
BootstrapAirframeNotes
d-flexaf-inlineFlex display (recipe-first)
d-flex flex-columnaf-stackVertical stack
d-flex flex-rowaf-inlineHorizontal inline
d-flex flex-wrapaf-inlineWrapping inline
d-gridaf-gridGrid display
d-noneaf-hiddenHidden
d-blockaf-blockBlock display
visually-hiddenaf-sr-onlyScreen readers
visually-hidden-focusableaf-sr-only-focusableSkip links use af-skip
BootstrapAirframeNotes
d-flex flex-wrap align-items-centeraf-clusterDefault semantic cluster
d-flex flex-wrap align-items-center (explicit)af-inline af-items-centerExplicit utilities
d-flex align-items-center justify-content-betweenaf-spreadDefault semantic spread
d-flex align-items-center justify-content-between (explicit)af-inline af-items-center af-justify-betweenExplicit utilities
d-flex align-items-center justify-content-centeraf-centerDefault semantic center
d-flex align-items-center justify-content-center (explicit)af-inline af-items-center af-justify-centerExplicit utilities
BootstrapAirframeNotes
text-centeraf-text-centerCenter text
text-startaf-text-startStart-aligned (LTR/RTL safe)
text-endaf-text-endEnd-aligned (LTR/RTL safe)
BootstrapAirframeNotes
w-100af-w-fullFull width

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.

BootstrapAirframeNotes
m-0af-m-0No margin
m-1af-m-xsExtra small
m-2af-m-smSmall
m-3af-m-mdMedium
m-4af-m-lgLarge
m-5af-m-xlExtra large
BootstrapAirframeNotes
p-0af-p-0No padding
p-1af-p-xsExtra small
p-2af-p-smSmall
p-3af-p-mdMedium
p-4af-p-lgLarge
p-5af-p-xlExtra large
<!-- 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.

<!-- 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>
<!-- 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.

<!-- 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>
<!-- 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