---
title: 'Bootstrap exporter'
description: 'Sass-path and CSS-variable-path Bootstrap themes — the exporter that replaces tint-color() with OKLCH.'
order: 11
---

# Bootstrap exporter

<div class="callout live-demos">
  <span class="callout-title">See it live</span>
  <p><a href="/transtyle/demo/acme/bootstrap/">Acme</a> · <a href="/transtyle/demo/cathode/bootstrap/">Cathode</a> · <a href="/transtyle/demo/govuk/bootstrap/">GOV.UK</a> · <a href="/transtyle/demo/carbon/bootstrap/">Carbon</a> — one page, four design systems, compiled to Bootstrap. <a href="/transtyle/demo/">All 32 demos →</a></p>
</div>

<!-- measured: acme.bootstrap.sass = 144 -->
<!-- measured: acme.bootstrap.decls = 215 -->
<!-- measured: acme.bootstrap.rows = 714 -->

Emits a **Bootstrap ≥5.3** theme along both consumption paths the Bootstrap community actually uses — on [Acme](/transtyle/docs/examples/), 144 Sass variables and 215 CSS custom properties, over 714 classified rows:

| File                        | Path          | Fidelity                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| --------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `_variables.transtyle.scss` | Sass          | full — imported **before** Bootstrap, so `$primary`, radii, fonts, spacers compile into every component                                                                                                                                                                                                                                                                                                                                                                                                          |
| `_maps.transtyle.scss`      | Sass          | full — imported **after** Bootstrap's variables; **replaces** its sRGB `tint-color()`/`shade-color()` derivations with our OKLCH-derived subtle/emphasis values                                                                                                                                                                                                                                                                                                                                                  |
| `bootstrap-theme.css`       | CSS variables | partial, documented — rethemes the token tier (`--bs-*`, utilities, body, borders, `[data-bs-theme=dark]` block) **and** the per-component variable layer: structure blocks (button/badge/toast paddings, radii, transitions) plus `.btn-<variant>` state colors from the role grid in both modes, so `.btn-primary` backgrounds/hovers follow your brand even without Sass. Still out of reach: values with no runtime variable (Sass-only expressions, responsive re-sets). Use it when you have no Sass build |

```json
"targets": { "bootstrap": { "output": "dist/bootstrap" } }
```

```scss
// your main.scss — the generated usage.md carries this exact order
@import 'bootstrap/scss/functions';
@import '../dist/bootstrap/variables.transtyle';
@import 'bootstrap/scss/variables';
@import 'bootstrap/scss/variables-dark';
@import '../dist/bootstrap/maps.transtyle';
@import 'bootstrap/scss/bootstrap';
```

## The interesting part: we out-derive Bootstrap on its own turf

Bootstrap generates `-bg-subtle` / `-border-subtle` / `-text-emphasis` per theme color by sRGB tinting. This exporter overrides those maps with the engine's OKLCH derivations — `<role>.on-tint` is the AA-checked "on-brand walk", `<role>.tint` a perceptual mix toward your surface, and `<role>.outline` (the border-subtle source) is now a first-class [role-grid](/transtyle/docs/language/#color-roles-the-role-grid) cell rather than a private exporter formula — so alerts and subtle badges stay perceptually consistent across all roles and both modes.

## Mapping highlights

| Bootstrap variable                                        | Comes from                                                                         | Note                                                                                                                                          |
| --------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `$primary…$danger`                                        | the same-named roles' `.solid`                                                     | `secondary…danger` usually `derived` on minimal systems                                                                                       |
| `$light` / `$dark`                                        | `neutral.tint` / `neutral.text-strong`                                             | exporter convention — Bootstrap's grayscale pseudo-roles have no IR slot                                                                      |
| `$theme-colors-text`                                      | `<role>.on-tint`                                                                   | the native binding the Phase 0 exercise predicted                                                                                             |
| `$theme-colors-bg-subtle` / `-border-subtle`              | `<role>.tint` / `<role>.outline`                                                   | cartesian-OKLab mixes, both engine-owned grid cells                                                                                           |
| `$body-*`, `$border-color` (+ `-dark` variants)           | `elevation.{0,1}.surface` / `text.base` / `text.muted` / `neutral.tint` / `border` |                                                                                                                                               |
| `$link-color`, `$focus-ring-color`                        | `primary`, `ring`                                                                  | dark-mode links ← `ring[dark]` (visibility-lightened)                                                                                         |
| `$border-radius{,-sm,-lg,-xl,-pill}`                      | the `radius.*` scale                                                               | `sm/lg/xl` derived from your `md`                                                                                                             |
| `$font-family-*`, type scale, `$spacers`, `$box-shadow*`  | fonts, the engine's `type.*`/`space.*` scales, scrim alpha ramps                   | scales report `derived` until you author them                                                                                                 |
| `$btn-*`, `$modal-*`, … (the 657-variable component tier) | `component.*` tokens + semantic defaults                                           | AL1: bound by meaning per the checked-in surface inventory — one coverage row per variable (driven, chained, or honestly dropped/unsupported) |
| `$grid-breakpoints`, `$display-font-sizes`                | the catalog _has_ these concepts                                                   | `unsupported` anyway — the ladders disagree (only `md`/768px matches on breakpoints), and rebinding would move behavior, not theme it         |
| `$box-shadow-inset`, `$container-max-widths`              | —                                                                                  | `unsupported`, reported honestly — no IR counterpart                                                                                          |

## Component theming

<!-- measured: bootstrap.surface.total = 952 -->
<!-- measured: bootstrap.surface.component = 657 -->

Bootstrap 5.3's `_variables.scss` has 952 top-level variables; 657 are component-scoped, and **every one of them is classified** against a checked-in surface inventory (`packages/exporter-bootstrap/surface-inventory.json`, drift-guarded in CI). To answer "which `$btn-*` variables does Transtyle drive, and via which path?" for any variable:

- **`report.json`** carries one coverage row per variable — its source slot (`component.button.radius`, `semantic.space.2`, …), its class (`native`/`derived`/`approximated`), or why it isn't driven (`dropped` structure and derivation knobs, `unsupported` IR gaps like icon assets and state opacities, each with a note).
- **Sass path**: bound variables are emitted in the component section of `_variables.transtyle.scss`; everything else follows Bootstrap's own `!default` chains from the roots Transtyle drives.
- **CSS path**: variables with a runtime `--bs-*` counterpart get selector-scoped overrides, and `.btn-<variant>` blocks carry state colors from the role grid.

Authoring is optional refinement: an empty `component` tier compiles forever from semantic defaults (Carbon's buttons come out square because _its_ `radius.control` is `0`), and an authored token wins on both paths — Acme authors `component.button.{radius,padding-x,padding-y}` and gets pill buttons in the [demo](/transtyle/docs/examples/). One coupling inherited from Bootstrap itself, documented rather than hidden: button and input padding share a single root (`$input-btn-padding-*`), so authored button padding moves form fields too — the same coupling PrimeNG's form-field theming expresses.

Dark mode follows Bootstrap's own mechanism (`data-bs-theme="dark"`) on both paths. One Sass-path caveat inherited from Bootstrap itself: `$primary` is a single value, so brand colors don't flip per mode — exactly how Bootstrap's own dark mode behaves.

See it running on real Bootstrap components: `npm run dev -w acme-demo-bootstrap` (or `cathode-demo-bootstrap` for the CRT version) in the [examples](/transtyle/docs/examples/).
