Skip to main content Skip to docs navigation

Sass

Utilize our source Sass files to take advantage of variables, maps, mixins, and functions to help you build faster and customize your project.

About Sass

Sass is a CSS preprocessor that adds features like variables, nesting, mixins, and functions to standard CSS. You write .scss files, compile them, and the output is plain CSS that browsers understand. If you're new to Sass, check out the Sass documentation to learn the basics.

Bootstrap is written in Sass. Our source .scss files use Sass maps to define design tokens, mixins to generate repetitive CSS patterns, and the @use/@forward module system to organize everything. When compiled, these Sass files produce the CSS that powers every Bootstrap component and utility. This lets you override token values, remove unused components, or extend the system with your own additions. If you don't need that level of control, you can skip Sass entirely and customize via CSS custom properties at runtime instead.

New to working with Sass and Bootstrap? Start with our npm guide for a step-by-step setup, or check out our examples repository for ready-to-use starter projects.

File structure

Whenever possible, avoid modifying Bootstrap's core files. For Sass, that means creating your own stylesheet that imports Bootstrap so you can modify and extend it. Assuming you're using a package manager like npm or yarn, you'll have a file structure that looks like this:

TEXT
your-project/
├── scss/
│   └── custom.scss
└── node_modules/
│   └── bootstrap/
│       ├── js/
│       └── scss/
└── index.html

If you've downloaded our source files and aren't using a package manager, you'll want to manually create something similar to that structure, keeping Bootstrap's source files separate from your own.

TEXT
your-project/
├── scss/
│   └── custom.scss
├── bootstrap/
│   ├── js/
│   └── scss/
└── index.html

Importing

In your custom.scss, you'll import Bootstrap's source Sass files. You have two options: include all of Bootstrap, or pick the parts you need. We encourage the latter, though be aware there are some requirements and dependencies across our components. You also will need to include some JavaScript for our plugins.

SCSS
// Custom.scss
// Option A: Include all of Bootstrap

@use "../node_modules/bootstrap/scss/bootstrap";

// Then add additional custom code here
SCSS
// Custom.scss
// Option B: Include parts of Bootstrap

// 1. Include root first (configuration, colors, tokens, and CSS layers)
@use "../node_modules/bootstrap/scss/root";

// 2. Include any other partials as needed
@use "../node_modules/bootstrap/scss/content";
@use "../node_modules/bootstrap/scss/layout";
@use "../node_modules/bootstrap/scss/forms";
@use "../node_modules/bootstrap/scss/buttons";

// 3. Components
@use "../node_modules/bootstrap/scss/alert";
@use "../node_modules/bootstrap/scss/card";
// ...

// 4. Helpers and utilities
@use "../node_modules/bootstrap/scss/helpers";
@use "../node_modules/bootstrap/scss/utilities/api";

// 5. Add additional custom code here

With that setup in place, you can begin to modify any of the Sass token maps in your custom.scss. We suggest using the full import stack from our bootstrap.scss file as your starting point.

SCSS
@forward "colors";

// Global CSS variables, layer definitions, and configuration
@forward "root";

// Subdir imports
@forward "content";
@forward "layout";
@forward "forms";
@forward "buttons";

// Components
@forward "accordion";
@forward "alert";
@forward "avatar";
@forward "badge";
@forward "breadcrumb";
@forward "chip";
@forward "card";
@forward "carousel";
@forward "datepicker";
@forward "dialog";
@forward "dropdown";
@forward "list-group";
@forward "nav";
@forward "nav-overflow";
@forward "navbar";
@forward "offcanvas";
@forward "pagination";
@forward "placeholder";
@forward "popover";
@forward "progress";
@forward "spinner";
@forward "stepper";
@forward "toasts";
@forward "tooltip";
@forward "transitions";

// Helpers
@forward "helpers";

// Utilities
@forward "utilities/api";

Compiling

In order to use your custom Sass code as CSS in the browser, you need a Sass compiler. Sass ships as a CLI package, but you can also compile it with other build tools like Gulp or Webpack, or with GUI applications. Some IDEs also have Sass compilers built in or as downloadable extensions.

We like to use the CLI to compile our Sass, but you can use whichever method you prefer. From the command line, run the following:

Shell
# Install Sass globally
npm install -g sass

# Watch your custom Sass for changes and compile it to CSS
sass --watch ./scss/custom.scss ./css/custom.css

Learn more about your options at sass-lang.com/install and compiling with VS Code.

Using Bootstrap with another build tool? Consider reading our guides for compiling with Webpack, Parcel, or Vite. We also have production-ready demos in our examples repository on GitHub.

Including

Once your CSS is compiled, you can include it in your HTML files. Inside your index.html you'll want to include your compiled CSS file. Be sure to update the path to your compiled CSS file if you've changed it.

HTML
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Custom Bootstrap</title>
    <link href="/css/custom.css" rel="stylesheet">
  </head>
  <body>
    <h1>Hello, world!</h1>
  </body>
</html>

Token architecture

Bootstrap v6 uses a layered token system built on CSS custom properties. Sass maps define the tokens at compile time, and CSS custom properties are generated from those maps for runtime use.

The layers, from lowest to highest level:

  1. Color scales (_colors.scss) — Base hue variables defined in oklch() generate a full scale of color steps (025–975) as CSS custom properties using color-mix(). For example, --blue-500, --red-200, --gray-900.

  2. Theme colors (_theme.scss) — The $theme-colors map defines semantic color roles like primary, success, and danger. Each theme color is a nested map with sub-keys: base, text, text-emphasis, bg, bg-subtle, bg-muted, border, focus-ring, and contrast.

  3. Global tokens (_theme.scss and _root.scss) — Semantic tokens for backgrounds ($theme-bgs), foregrounds ($theme-fgs), and borders ($theme-borders) are defined alongside the $root-tokens map, which aggregates everything into :root CSS custom properties.

  4. Component tokens (e.g., _alert.scss) — Each component defines its own $*-tokens map (e.g., $alert-tokens) that gets output as CSS custom properties scoped to the component's class.

Token defaults

Every token map in Bootstrap includes the !default flag, allowing you to override values before Bootstrap's files are loaded. Since Bootstrap uses Sass modules (@use/@forward), you can override !default variables using the with () syntax. For CSS custom property tokens, you have two customization paths:

  1. At compile time — Override the Sass maps that generate the CSS custom properties.
  2. At runtime — Override the CSS custom properties directly in your stylesheet, no Sass required.

Compile-time overrides

To override token values at compile time, use @use ... with () when importing Bootstrap. You only need to include the keys you want to change — Bootstrap merges your overrides with its defaults using the defaults() function, so all other tokens remain intact.

SCSS
@use "../node_modules/bootstrap/scss/bootstrap" with (
  $root-tokens: (
    --border-radius: .25rem,
    --spacer: 1.5rem,
  ),
  $alert-tokens: (
    --alert-padding-x: 2rem,
    --alert-border-radius: 1rem,
  )
);

Under the hood, every token map uses a two-step pattern:

SCSS
$alert-tokens: () !default;
$alert-tokens: defaults(
  (
    // Default tokens here
  ),
  $alert-tokens
);

The () !default declaration receives your with() overrides. Then defaults() merges your entries on top of the built-in defaults, so unspecified keys keep their original values. Any keys set to null are removed from the map entirely, letting you drop tokens you don't need.

Runtime overrides

Because tokens are output as CSS custom properties, you can override them directly in CSS without any Sass compilation:

CSS
:root {
  --border-radius: .25rem;
  --primary-base: oklch(55% 0.25 260);
  --bg-body: #f8f9fa;
}

You can also scope overrides to specific components:

CSS
.alert {
  --alert-padding-x: 2rem;
  --alert-padding-y: 1.5rem;
  --alert-border-radius: 1rem;
}

Get started with Bootstrap via npm with our starter project! Head to the Sass & JS example template repository to see how to build and customize Bootstrap in your own npm project. Includes Sass compiler, Autoprefixer, Stylelint, PurgeCSS, and Bootstrap Icons.

Maps and loops

Bootstrap includes several Sass maps that generate families of related CSS custom properties. These maps all include the !default flag and can be overridden.

Theme color map

The $theme-colors map defines the semantic color palette. Each entry is a nested map with sub-keys like base, text, bg, border, and more. See the Theme documentation for the full map, sub-key reference, and usage examples.

To modify, add, or remove theme colors at compile time:

SCSS
@use "sass:map";
@use "../node_modules/bootstrap/scss/theme" with (
  $theme-colors: (
    "primary": (
      "base": var(--indigo-500),
      "text": light-dark(var(--indigo-600), var(--indigo-400)),
      "bg": var(--indigo-500),
      "bg-subtle": light-dark(var(--indigo-100), var(--indigo-900)),
      "border": light-dark(var(--indigo-300), var(--indigo-600)),
      "focus-ring": light-dark(color-mix(in oklch, var(--indigo-500) 50%, var(--bg-body)), color-mix(in oklch, var(--indigo-500) 75%, var(--bg-body))),
      "contrast": var(--white)
    )
  )
);

Global token maps

Background ($theme-bgs), foreground ($theme-fgs), and border ($theme-borders) token maps define semantic layer colors. These are also covered in the Theme documentation.

Root tokens ($root-tokens)

The $root-tokens map aggregates global design tokens like fonts, spacing, borders, and form variables. Theme colors, backgrounds, foregrounds, and border tokens are merged into it before output.

SCSS
// stylelint-disable @stylistic/value-list-max-empty-lines, @stylistic/function-max-empty-lines
// stylelint-disable-next-line scss/dollar-variable-default
$root-tokens: defaults(
  (
    --black: #{$black},
    --white: #{$white},

    --gradient: #{$gradient},

    // scss-docs-start root-font-size-variables
    --font-size-base: 1rem,
    --font-size-xs: .75rem,
    --font-size-sm: .875rem,
    --font-size-md: 1rem,
    --font-size-lg: clamp(1.25rem, 1rem + .625vw, 1.5rem),
    --font-size-xl: clamp(1.5rem, 1.1rem + .75vw, 1.75rem),
    --font-size-2xl: clamp(1.75rem, 1.3rem + 1vw, 2rem),
    --font-size-3xl: clamp(2rem, 1.5rem + 1.875vw, 2.5rem),
    --font-size-4xl: clamp(2.25rem, 1.75rem + 2.5vw, 3rem),
    --font-size-5xl: clamp(3rem, 2rem + 5vw, 4rem),
    --font-size-6xl: clamp(3.75rem, 2.5rem + 6.25vw, 5rem),

    --line-height-xs: 1.25,
    --line-height-sm: 1.5,
    --line-height-md: 1.5,
    --line-height-lg: 1.5,
    --line-height-xl: calc(2.5 / 1.75),
    --line-height-2xl: calc(3 / 2.25),
    --line-height-3xl: 1.2,
    --line-height-4xl: 1.1,
    --line-height-5xl: 1.1,
    --line-height-6xl: 1,
    // scss-docs-end root-font-size-variables

    // scss-docs-start root-font-weight-variables
    --font-weight-lighter: lighter,
    --font-weight-light: 300,
    --font-weight-normal: 400,
    --font-weight-medium: 500,
    --font-weight-semibold: 600,
    --font-weight-bold: 700,
    --font-weight-bolder: bolder,
    // scss-docs-end root-font-weight-variables

    // scss-docs-start root-body-variables
    --body-font-family: system-ui,
    --body-font-size: var(--font-size-base),
    --body-font-weight: #{$font-weight-base},
    --body-line-height: #{$line-height-base},

    --heading-color: #{$headings-color},

    --hr-border-color: var(--border-color),

    --link-color: light-dark(var(--primary-base), var(--primary-text)),
    --link-decoration: #{$link-decoration},
    --link-hover-color: color-mix(in oklch, var(--link-color) 90%, #000),

    --font-mono: "SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
    --code-font-size: 95%,
    --code-color: var(--fg-2),

    // scss-docs-start root-border-var
    --border-width: #{$border-width},
    --border-style: #{$border-style},
    --border-color: light-dark(color-mix(in oklch, var(--gray-100), var(--gray-200)), var(--gray-700)),
    --border-color-translucent: color-mix(in oklch, var(--fg-body) 15%, transparent),
    // scss-docs-end root-border-var

    // scss-docs-start root-border-radius-var
    --border-radius: .5rem,
    --border-radius-xs: .375rem,
    --border-radius-sm: .5rem,
    --border-radius-lg: .75rem,
    --border-radius-xl: 1rem,
    --border-radius-2xl: 2rem,
    --border-radius-pill: 50rem,
    // scss-docs-end root-border-radius-var

    // scss-docs-start root-box-shadow-variables
    --box-shadow: 0 .5rem 1rem rgb(0 0 0 / 15%),
    --box-shadow-sm: 0 .125rem .25rem rgb(0 0 0 / 7.5%),
    --box-shadow-lg: 0 1rem 3rem rgb(0 0 0 / 17.5%),
    --box-shadow-inset: inset 0 1px 2px rgb(0 0 0 / 7.5%),
    // scss-docs-end root-box-shadow-variables

    --spacer: 1rem,

    // scss-docs-start root-focus-variables
    --focus-ring-width: 3px,
    --focus-ring-offset: 1px,
    --focus-ring-color: var(--primary-focus-ring),
    --focus-ring: var(--focus-ring-width) solid var(--focus-ring-color),
    // scss-docs-end root-focus-variables

    // scss-docs-start root-form-variables
    --control-checked-bg: var(--primary-base),
    --control-checked-border-color: var(--control-checked-bg),
    --control-active-bg: var(--primary-base),
    --control-active-border-color: var(--control-active-bg),
    --control-disabled-bg: var(--bg-3),
    --control-disabled-opacity: .65,

    --btn-input-fg: var(--fg-body),
    --btn-input-bg: var(--bg-body),

    --btn-input-min-height: 2.5rem,
    --btn-input-padding-y: .375rem,
    --btn-input-padding-x: .75rem,
    --btn-input-font-size: var(--font-size-base),
    --btn-input-line-height: var(--line-height-base),
    --btn-input-border-radius: var(--border-radius),

    --btn-input-xs-min-height: 1.5rem,
    --btn-input-xs-padding-y: .125rem,
    --btn-input-xs-padding-x: .5rem,
    --btn-input-xs-font-size: var(--font-size-xs),
    --btn-input-xs-line-height: 1.125,
    --btn-input-xs-border-radius: var(--border-radius-xs),

    --btn-input-sm-min-height: 2.25rem,
    --btn-input-sm-padding-y: .25rem,
    --btn-input-sm-padding-x: .625rem,
    --btn-input-sm-font-size: var(--font-size-sm),
    --btn-input-sm-line-height: var(--line-height-sm),
    --btn-input-sm-border-radius: var(--border-radius-sm),

    --btn-input-lg-min-height: 3rem,
    --btn-input-lg-padding-y: .5rem,
    --btn-input-lg-padding-x: 1rem,
    --btn-input-lg-font-size: var(--font-size-md),
    --btn-input-lg-line-height: var(--line-height-md),
    --btn-input-lg-border-radius: var(--border-radius-lg),
    // scss-docs-end root-form-variables

    // scss-docs-start root-form-validation-variables
    --form-valid-color: #{$form-valid-color},
    --form-valid-border-color: #{$form-valid-border-color},
    --form-invalid-color: #{$form-invalid-color},
    --form-invalid-border-color: #{$form-invalid-border-color},
    // scss-docs-end root-form-validation-variables
  ),
  $root-tokens
);
// stylelint-enable @stylistic/value-list-max-empty-lines, @stylistic/function-max-empty-lines

All root tokens are output to :root as CSS custom properties:

SCSS
// Generate semantic theme colors
@each $color-name, $color-map in $theme-colors {
  @each $key, $value in $color-map {
    $root-tokens: map.set($root-tokens, --#{$color-name}-#{$key}, $value);
  }
}

// Generate background tokens
@each $key, $value in $theme-bgs {
  $root-tokens: map.set($root-tokens, --bg-#{$key}, $value);
}

// Generate foreground tokens
@each $key, $value in $theme-fgs {
  $root-tokens: map.set($root-tokens, --fg-#{$key}, $value);
}

// Generate border tokens
@each $key, $value in $theme-borders {
  $root-tokens: map.set($root-tokens, --border-#{$key}, $value);
}

Component tokens

Each component defines its own token map that gets output as CSS custom properties scoped to the component's class. This makes components self-contained and easy to customize.

Here's the alert component as an example:

SCSS
// stylelint-disable-next-line scss/dollar-variable-default
$alert-tokens: defaults(
  (
    --alert-gap: #{$spacer * .75},
    --alert-bg: var(--theme-bg-subtle, var(--bg-1)),
    --alert-padding-x: #{$spacer},
    --alert-padding-y: #{$spacer},
    --alert-color: var(--theme-text, inherit),
    --alert-border-color: var(--theme-border, var(--border-color)),
    --alert-border: var(--border-width) solid var(--alert-border-color),
    --alert-border-radius: var(--border-radius),
    --alert-link-color: inherit,
    --hr-border-color: var(--theme-border, var(--border-color)),
  ),
  $alert-tokens
);

The tokens are output on the .alert class using the tokens() mixin:

SCSS
.alert {
  @include tokens($alert-tokens);

  padding: var(--alert-padding-y) var(--alert-padding-x);
  color: var(--alert-color);
  background-color: var(--alert-bg);
  border: var(--alert-border);
  // ...
}

Override Sass tokens

Use with () to override specific component token values at compile time with Sass. Your overrides are merged with the defaults, so you only need to specify the keys you want to change:

SCSS
@use "../node_modules/bootstrap/scss/bootstrap" with (
  $alert-tokens: (
    --alert-padding-x: 2rem,
    --alert-bg: var(--bg-2),
    --alert-border-radius: 0,
  )
);

This works for every component: $card-tokens, $button-tokens, $dropdown-tokens, $nav-tokens, and so on — any $*-tokens map supports partial overrides through the same merge pattern.

Remove tokens with null

Set any map key to null to remove it entirely. The defaults() function strips null entries before the map is used, so no CSS is generated for those keys:

SCSS
@use "../node_modules/bootstrap/scss/bootstrap" with (
  $alert-tokens: (
    --alert-margin-bottom: null,
  )
);

Override CSS tokens

You can also override the CSS custom properties directly without Sass:

CSS
.alert {
  --alert-padding-x: 2rem;
  --alert-bg: var(--bg-2);
  --alert-border-radius: 0;
}

Size maps

In addition to token maps, Bootstrap uses size maps to generate size variant classes like .btn-sm, .btn-lg, .form-control-sm, etc. These use the same defaults() function and support the same override and removal patterns.

Size maps accept a simple list of size names:

SCSS
$button-sizes: () !default;
$button-sizes: defaults(
  ("xs", "sm", "lg"),
  $button-sizes
);

The loop then derives CSS custom property values from each size name:

SCSS
@each $size, $_ in $button-sizes {
  .btn-#{$size} {
    --btn-min-height: var(--btn-input-#{$size}-min-height);
    --btn-padding-y: var(--btn-input-#{$size}-padding-y);
    // ...
  }
}

To remove a size, set its key to null via with():

SCSS
@use "../node_modules/bootstrap/scss/bootstrap" with (
  $button-sizes: ("xs": null),
);

Variant maps

Some components use variant maps to generate style variants. For example, $button-variants defines solid, outline, subtle, and text button styles. These are also processed by defaults() and support null removal:

SCSS
@use "../node_modules/bootstrap/scss/bootstrap" with (
  $button-variants: (
    "text": null,
    "subtle": null,
  ),
);

This removes the .btn-text and .btn-subtle classes from the compiled CSS while keeping .btn-solid and .btn-outline intact.

Required keys

Bootstrap assumes the presence of some specific keys within Sass maps as we use and extend these ourselves. As you customize the included maps, you may encounter errors where a specific Sass map's key is being used.

For $theme-colors, the primary, success, and danger keys are required for links, buttons, and form states. Replacing the values of these keys should present no issues, but removing them may cause Sass compilation issues.

Functions

Defaults

The defaults() function is the backbone of Bootstrap's customization system. It merges user overrides on top of built-in defaults and strips any null entries from the result. Every token map, size map, and variant map uses it:

SCSS
@function defaults($defaults, $overrides) { ... }

It accepts either a map or a list as the first argument. Lists are automatically converted to maps with true values, which is how size maps like $button-sizes work with a clean ("xs", "sm", "lg") syntax.

Colors

Base colors are defined as oklch() values and expanded into full scales via color-mix(). Each hue generates steps from 025 to 975:

SCSS
// Example generated custom properties from _colors.scss
--blue-025: color-mix(in lab, #fff 94%, oklch(60% 0.24 240));
--blue-050: color-mix(in lab, #fff 90%, oklch(60% 0.24 240));
--blue-100: color-mix(in lab, #fff 80%, oklch(60% 0.24 240));
// ...
--blue-500: oklch(60% 0.24 240);
// ...
--blue-900: color-mix(in lab, #000 64%, oklch(60% 0.24 240));
--blue-975: color-mix(in lab, #000 88%, oklch(60% 0.24 240));

You can reference any color scale step as a CSS custom property: var(--blue-500), var(--red-200), var(--gray-900), etc.

Color contrast

In order to meet the Web Content Accessibility Guidelines (WCAG) contrast requirements, authors must provide a minimum text color contrast of 4.5:1 and a minimum non-text color contrast of 3:1, with very few exceptions.

To help with this, we included the color-contrast function in Bootstrap. It uses the WCAG contrast ratio algorithm for calculating contrast thresholds based on relative luminance in an sRGB color space to automatically return a light (#fff), dark (#212529) or black (#000) contrast color based on the specified base color. This function is especially useful for mixins or loops where you're generating multiple classes.

For example, to generate color swatches from our $theme-colors map:

SCSS
@each $color, $value in $theme-colors {
  .swatch-#{$color} {
    color: color-contrast(map.get($value, "base"));
  }
}

Escape SVG

We use the escape-svg function to escape the <, > and # characters for SVG background images. When using the escape-svg function, data URIs must be quoted.

Add and Subtract functions

We use the add and subtract functions to wrap the CSS calc function. The primary purpose of these functions is to avoid errors when a "unitless" 0 value is passed into a calc expression. Expressions like calc(10px - 0) will return an error in all browsers, despite being mathematically correct.

Example where the calc is valid:

SCSS
$border-radius: .25rem;
$border-width: 1px;

.element {
  // Output calc(.25rem - 1px) is valid
  border-radius: calc($border-radius - $border-width);
}

.element {
  // Output the same calc(.25rem - 1px) as above
  border-radius: subtract($border-radius, $border-width);
}

Example where the calc is invalid:

SCSS
$border-radius: .25rem;
$border-width: 0;

.element {
  // Output calc(.25rem - 0) is invalid
  border-radius: calc($border-radius - $border-width);
}

.element {
  // Output .25rem
  border-radius: subtract($border-radius, $border-width);
}

Mixins

Our scss/mixins/ directory has a ton of mixins that power parts of Bootstrap and can also be used across your own project.

Tokens

The tokens() mixin outputs a Sass map as CSS custom properties. Every component uses it to render its token map:

SCSS
@mixin tokens($map) {
  @each $prop, $value in $map {
    #{$prop}: #{$value};
  }
}
SCSS
.alert {
  @include tokens($alert-tokens);
  // Outputs: --alert-padding-x: ...; --alert-padding-y: ...; etc.
}

Color schemes

A shorthand mixin for the prefers-color-scheme media query is available with support for light and dark color schemes. See the color modes documentation for information on our color mode mixin.

SCSS
@mixin color-scheme($name) {
  @media (prefers-color-scheme: #{$name}) {
    @content;
  }
}
SCSS
.custom-element {
  @include color-scheme(light) {
    // Insert light mode styles here
  }

  @include color-scheme(dark) {
    // Insert dark mode styles here
  }
}