},
{
"path": "./dist/css/bootstrap.css",
- "maxSize": "37.5 kB"
+ "maxSize": "37.75 kB"
},
{
"path": "./dist/css/bootstrap.min.css",
$btn-border-radius-lg: var(--#{$prefix}border-radius-lg) !default;
$btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
-
-$btn-hover-bg-shade-amount: 15% !default;
-$btn-hover-bg-tint-amount: 15% !default;
-$btn-hover-border-shade-amount: 20% !default;
-$btn-hover-border-tint-amount: 10% !default;
-$btn-active-bg-shade-amount: 20% !default;
-$btn-active-bg-tint-amount: 20% !default;
-$btn-active-border-shade-amount: 25% !default;
-$btn-active-border-tint-amount: 10% !default;
// scss-docs-end btn-variables
display: inline-flex;
vertical-align: middle; // match .btn alignment given font-size hack above
- > .btn {
+ > .btn,
+ > .btn-check {
position: relative;
flex: 1 1 auto;
}
// Bring the hover, focused, and "active" buttons to the front to overlay
// the borders properly
- > .btn-check:checked + .btn,
- > .btn-check:focus + .btn,
+ > .btn-check:has(input:checked),
+ > .btn-check:has(input:focus),
> .btn:hover,
> .btn:focus,
> .btn:active,
@include border-radius($btn-border-radius);
// Prevent double borders when buttons are next to each other
- > :not(.btn-check:first-child) + .btn,
+ > .btn:not(:first-child),
+ > .btn-check:not(:first-child),
> .btn-group:not(:first-child) {
margin-inline-start: calc(-1 * #{$btn-border-width});
}
// Reset rounded corners
> .btn:not(:last-child):not(.dropdown-toggle),
+ > .btn-check:not(:last-child),
> .btn.dropdown-toggle-split:first-child,
> .btn-group:not(:last-child) > .btn {
@include border-end-radius(0);
}
- // The left radius should be 0 if the button is:
- // - the "third or more" child
- // - the second child and the previous element isn't `.btn-check` (making it the first child visually)
- // - part of a btn-group which isn't the first child
- > .btn:nth-child(n + 3),
- > :not(.btn-check) + .btn,
+ // The left radius should be 0 if the button is not the first child
+ > .btn:not(:first-child),
+ > .btn-check:not(:first-child),
> .btn-group:not(:first-child) > .btn {
@include border-start-radius(0);
}
justify-content: center;
> .btn,
+ > .btn-check,
> .btn-group {
width: 100%;
}
> .btn:not(:first-child),
+ > .btn-check:not(:first-child),
> .btn-group:not(:first-child) {
margin-top: calc(-1 * #{$btn-border-width});
}
// Reset rounded corners
> .btn:not(:last-child):not(.dropdown-toggle),
+ > .btn-check:not(:last-child),
> .btn-group:not(:last-child) > .btn {
@include border-bottom-radius(0);
}
- // The top radius should be 0 if the button is:
- // - the "third or more" child
- // - the second child and the previous element isn't `.btn-check` (making it the first child visually)
- // - part of a btn-group which isn't the first child
- > .btn:nth-child(n + 3),
- > :not(.btn-check) + .btn,
+ // The top radius should be 0 if the button is not the first child
+ > .btn:not(:first-child),
+ > .btn-check:not(:first-child),
> .btn-group:not(:first-child) > .btn {
@include border-top-radius(0);
}
@use "../forms/form-variables" as *;
// scss-docs-start btn-variables
-$btn-color: var(--#{$prefix}color-body) !default;
+$btn-color: var(--#{$prefix}fg-body) !default;
$btn-padding-y: $input-btn-padding-y !default;
$btn-padding-x: $input-btn-padding-x !default;
$btn-font-family: $input-btn-font-family !default;
$btn-line-height: $input-btn-line-height !default;
$btn-white-space: null !default; // Set to `nowrap` to prevent text wrapping
+$btn-padding-y-xs: .125rem !default;
+$btn-padding-x-xs: .375rem !default;
+$btn-font-size-xs: var(--#{$prefix}font-size-xs) !default;
+$btn-line-height-xs: 1.125rem !default;
+
$btn-padding-y-sm: $input-btn-padding-y-sm !default;
$btn-padding-x-sm: $input-btn-padding-x-sm !default;
-$btn-font-size-sm: $input-btn-font-size-sm !default;
+$btn-font-size-sm: var(--#{$prefix}font-size-sm) !default;
+$btn-line-height-sm: 1.125rem !default;
$btn-padding-y-lg: $input-btn-padding-y-lg !default;
$btn-padding-x-lg: $input-btn-padding-x-lg !default;
-$btn-font-size-lg: $input-btn-font-size-lg !default;
+$btn-font-size-lg: 16px !default;
+$btn-line-height-lg: 1.25rem !default;
+
+// Intentionally left for folks who want it
+// $btn-padding-y-xl: .75rem !default;
+// $btn-padding-x-xl: 1.25rem !default;
+// $btn-font-size-xl: var(--#{$prefix}font-size-lg) !default;
+// $btn-line-height-xl: 1.5rem !default;
$btn-border-width: $input-btn-border-width !default;
$btn-font-weight: $font-weight-normal !default;
$btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default;
-// $btn-focus-width: $input-btn-focus-width !default;
-// $btn-focus-box-shadow: $input-btn-focus-box-shadow !default;
$btn-disabled-opacity: .65 !default;
$btn-active-box-shadow: inset 0 3px 5px rgba($black, .125) !default;
$btn-link-color: var(--#{$prefix}link-color) !default;
$btn-link-hover-color: var(--#{$prefix}link-hover-color) !default;
-$btn-link-disabled-color: var(--#{$prefix}gray-600) !default;
-// $btn-link-color-contrast: color-contrast($link-color) !default;
-// $btn-link-focus-shadow-rgb: to-rgb(color.mix($btn-link-color-contrast, $link-color, 15%)) !default;
+$btn-link-disabled-color: var(--#{$prefix}fg-3) !default;
// Allows for customizing button radius independently from global border radius
$btn-border-radius: var(--#{$prefix}border-radius) !default;
+$btn-border-radius-xs: var(--#{$prefix}border-radius-sm) !default;
$btn-border-radius-sm: var(--#{$prefix}border-radius-sm) !default;
$btn-border-radius-lg: var(--#{$prefix}border-radius-lg) !default;
+$btn-border-radius-xl: var(--#{$prefix}border-radius-lg) !default;
$btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
-
-$btn-hover-bg-shade-amount: 15% !default;
-$btn-hover-bg-tint-amount: 15% !default;
-$btn-hover-border-shade-amount: 20% !default;
-$btn-hover-border-tint-amount: 10% !default;
-$btn-active-bg-shade-amount: 20% !default;
-$btn-active-bg-tint-amount: 20% !default;
-$btn-active-border-shade-amount: 25% !default;
-$btn-active-border-tint-amount: 10% !default;
// scss-docs-end btn-variables
@use "sass:list";
@use "sass:map";
@use "sass:meta";
+@use "sass:string";
@use "../colors" as *;
@use "../config" as *;
@use "../theme" as *;
) !default;
// scss-docs-end btn-variants
-// // Main button style generator mixin
-
-// Generate button variant classes (e.g., .btn-solid, .btn-outline, etc.)
-// scss-docs-start btn-variant-mixin
-@each $variant, $_ in $button-variants {
- .btn-#{$variant} {
- @each $property, $value in map.get($button-variants, $variant, "base") {
- @if $value == "transparent" {
- --#{$prefix}btn-#{$property}: transparent;
- } @else {
- --#{$prefix}btn-#{$property}: var(--#{$prefix}theme-#{$value});
- }
- }
-
- &:hover {
- @each $property, $value in map.get($button-variants, $variant, "hover") {
- @if $value == "transparent" {
- --#{$prefix}btn-hover-#{$property}: transparent;
- } @else if meta.type-of($value) == "list" {
- $first-value: list.nth($value, 1);
- $second-value: list.nth($value, 2);
- --#{$prefix}btn-hover-#{$property}: color-mix(in oklch, var(--#{$prefix}theme-#{$first-value}) 50%, var(--#{$prefix}theme-#{$second-value}));
- } @else if $value == "bg-subtle" {
- --#{$prefix}btn-hover-#{$property}: var(--#{$prefix}theme-#{$value});
- } @else {
- --#{$prefix}btn-hover-#{$property}: oklch(from var(--#{$prefix}theme-#{$value}) calc(l * .95) calc(c * 1.1) h);
- }
- }
- }
-
- &:focus-visible {
- outline-color: var(--#{$prefix}theme-focus-ring);
- }
-
- &:active,
- &.active {
- @each $property, $value in map.get($button-variants, $variant, "active") {
- @if $value == "transparent" {
- --#{$prefix}btn-active-#{$property}: transparent;
- } @else if $value == "bg-subtle" {
- --#{$prefix}btn-active-#{$property}: var(--#{$prefix}theme-#{$value});
- } @else {
- --#{$prefix}btn-active-#{$property}: oklch(from var(--#{$prefix}theme-#{$value}) calc(l * .9) calc(c * 1.15) h);
- }
- }
- }
- }
-}
-// scss-docs-end btn-variant-mixin
-
-// scss-docs-start btn-size-mixin
-@mixin button-size($padding-y, $padding-x, $font-size, $border-radius) {
- --#{$prefix}btn-padding-y: #{$padding-y};
- --#{$prefix}btn-padding-x: #{$padding-x};
- @include rfs($font-size, --#{$prefix}btn-font-size);
- --#{$prefix}btn-border-radius: #{$border-radius};
-}
-// scss-docs-end btn-size-mixin
+// scss-docs-start btn-sizes
+$button-sizes: (
+ "xs": (
+ "padding-y": $btn-padding-y-xs,
+ "padding-x": $btn-padding-x-xs,
+ "font-size": $btn-font-size-xs,
+ "line-height": $btn-line-height-xs,
+ "border-radius": $btn-border-radius-xs,
+ "min-height": 1.5rem
+ ),
+ "sm": (
+ "padding-y": $btn-padding-y-sm,
+ "padding-x": $btn-padding-x-sm,
+ "font-size": $btn-font-size-sm,
+ "line-height": $btn-line-height-sm,
+ "border-radius": $btn-border-radius-sm,
+ "min-height": 2rem
+ ),
+ "lg": (
+ "padding-y": $btn-padding-y-lg,
+ "padding-x": $btn-padding-x-lg,
+ "font-size": $btn-font-size-lg,
+ "line-height": $btn-line-height-lg,
+ "border-radius": $btn-border-radius-lg,
+ "min-height": 2.75rem
+ ),
+ // Commented out by default on purpose
+ // "xl": (
+ // "padding-y": $btn-padding-y-xl,
+ // "padding-x": $btn-padding-x-xl,
+ // "font-size": $btn-font-size-xl,
+ // "line-height": $btn-line-height-xl,
+ // "border-radius": $btn-border-radius-xl,
+ // "min-height": 3.25rem
+ // ),
+) !default;
+// scss-docs-end btn-sizes
//
// Base styles
//
+$btn-variant-selectors: () !default;
+@each $variant in map.keys($button-variants) {
+ $btn-variant-selectors: list.append($btn-variant-selectors, string.unquote(".btn-#{$variant}"), comma);
+}
+
@layer components {
.btn,
- [class*="btn-"] {
+ #{$btn-variant-selectors} {
// scss-docs-start btn-css-vars
+ --#{$prefix}btn-min-height: 2.25rem;
--#{$prefix}btn-padding-x: #{$btn-padding-x};
--#{$prefix}btn-padding-y: #{$btn-padding-y};
--#{$prefix}btn-font-family: #{$btn-font-family};
--#{$prefix}btn-font-weight: #{$btn-font-weight};
--#{$prefix}btn-line-height: #{$btn-line-height};
--#{$prefix}btn-color: #{$btn-color};
- --#{$prefix}btn-bg: transparent;
--#{$prefix}btn-border-width: #{$btn-border-width};
--#{$prefix}btn-border-color: transparent;
--#{$prefix}btn-border-radius: #{$btn-border-radius};
--#{$prefix}btn-hover-border-color: transparent;
- --#{$prefix}btn-box-shadow: #{$btn-box-shadow};
--#{$prefix}btn-disabled-opacity: #{$btn-disabled-opacity};
- // --#{$prefix}btn-focus-box-shadow: 0 0 0 #{$btn-focus-width} rgba(var(--#{$prefix}btn-focus-shadow-rgb), .5);
// scss-docs-end btn-css-vars
- display: inline-block;
+ display: inline-flex;
+ gap: var(--#{$prefix}btn-gap, .25rem);
+ align-items: center;
+ justify-content: center;
+ min-height: var(--#{$prefix}btn-min-height);
padding: var(--#{$prefix}btn-padding-y) var(--#{$prefix}btn-padding-x);
font-family: var(--#{$prefix}btn-font-family);
@include font-size(var(--#{$prefix}btn-font-size));
font-weight: var(--#{$prefix}btn-font-weight);
line-height: var(--#{$prefix}btn-line-height);
color: var(--#{$prefix}btn-color);
- text-align: center;
text-decoration: none;
white-space: $btn-white-space;
vertical-align: middle;
cursor: if($enable-button-pointers, pointer, null);
user-select: none;
+ background-color: var(--#{$prefix}btn-bg, var(--#{$prefix}bg-2));
border: var(--#{$prefix}btn-border-width) solid var(--#{$prefix}btn-border-color);
@include border-radius(var(--#{$prefix}btn-border-radius));
- @include gradient-bg(var(--#{$prefix}btn-bg));
- @include box-shadow(var(--#{$prefix}btn-box-shadow));
@include transition($btn-transition);
&:hover {
color: var(--#{$prefix}btn-hover-color);
- background-color: var(--#{$prefix}btn-hover-bg);
+ background-color: var(--#{$prefix}btn-hover-bg, var(--#{$prefix}bg-3));
border-color: var(--#{$prefix}btn-hover-border-color);
}
- .btn-check + &:hover {
- // override for the checkbox/radio buttons
- color: var(--#{$prefix}btn-color);
- background-color: var(--#{$prefix}btn-bg);
- border-color: var(--#{$prefix}btn-border-color);
- }
-
&:focus-visible {
@include focus-ring(true);
--#{$prefix}focus-ring-offset: 1px;
}
- .btn-check:focus-visible + & {
- @include focus-ring(true);
- }
-
- .btn-check:checked + &,
- :not(.btn-check) + &:active,
- &:first-child:active,
&.active,
&.show {
color: var(--#{$prefix}btn-active-color);
- background-color: var(--#{$prefix}btn-active-bg);
- // Remove CSS gradients if they're enabled
- background-image: if($enable-gradients, none, null);
+ background-color: var(--#{$prefix}btn-active-bg, var(--#{$prefix}bg-3));
border-color: var(--#{$prefix}btn-active-border-color);
- @include box-shadow(var(--#{$prefix}btn-active-shadow));
&:focus-visible {
@include focus-ring(true);
}
}
- .btn-check:checked:focus-visible + & {
- @include focus-ring(true);
- }
-
&:disabled,
&.disabled,
fieldset:disabled & {
color: var(--#{$prefix}btn-disabled-color);
pointer-events: none;
- background-color: var(--#{$prefix}btn-disabled-bg);
+ background-color: var(--#{$prefix}btn-disabled-bg, var(--#{$prefix}bg-1));
background-image: if($enable-gradients, none, null);
border-color: var(--#{$prefix}btn-disabled-border-color);
opacity: var(--#{$prefix}btn-disabled-opacity);
- @include box-shadow(none);
}
}
+
+ // Main button style generator mixin
+ // Generate button variant classes (e.g., .btn-solid, .btn-outline, etc.)
+ // scss-docs-start btn-variant-mixin
+ @each $variant, $_ in $button-variants {
+ .btn-#{$variant} {
+ @each $property, $value in map.get($button-variants, $variant, "base") {
+ @if $value == "transparent" {
+ --#{$prefix}btn-#{$property}: transparent;
+ } @else {
+ --#{$prefix}btn-#{$property}: var(--#{$prefix}theme-#{$value});
+ }
+ }
+
+ @each $property, $value in map.get($button-variants, $variant, "active") {
+ @if $value == "transparent" {
+ --#{$prefix}btn-active-#{$property}: transparent;
+ } @else if $value == "bg-subtle" {
+ --#{$prefix}btn-active-#{$property}: var(--#{$prefix}theme-#{$value});
+ } @else {
+ --#{$prefix}btn-active-#{$property}: oklch(from var(--#{$prefix}theme-#{$value}) calc(l * .9) calc(c * 1.15) h);
+ }
+ }
+ @each $property, $value in map.get($button-variants, $variant, "base") {
+ @if $value == "transparent" {
+ --#{$prefix}btn-disabled-#{$property}: transparent;
+ } @else {
+ --#{$prefix}btn-disabled-#{$property}: var(--#{$prefix}theme-#{$value});
+ }
+ }
+
+
+ &:hover {
+ @each $property, $value in map.get($button-variants, $variant, "hover") {
+ @if $value == "transparent" {
+ --#{$prefix}btn-hover-#{$property}: transparent;
+ } @else if meta.type-of($value) == "list" {
+ $first-value: list.nth($value, 1);
+ $second-value: list.nth($value, 2);
+ --#{$prefix}btn-hover-#{$property}: color-mix(in oklch, var(--#{$prefix}theme-#{$first-value}) 50%, var(--#{$prefix}theme-#{$second-value}));
+ } @else if $value == "bg-subtle" {
+ --#{$prefix}btn-hover-#{$property}: var(--#{$prefix}theme-#{$value});
+ } @else {
+ --#{$prefix}btn-hover-#{$property}: oklch(from var(--#{$prefix}theme-#{$value}) calc(l * .95) calc(c * 1.1) h);
+ }
+ }
+ }
+
+ &:focus-visible {
+ outline-color: var(--#{$prefix}theme-focus-ring);
+ }
+
+ &:active,
+ &.active,
+ &.btn-check:has(input:checked) {
+ @each $property, $value in map.get($button-variants, $variant, "active") {
+ @if $value == "transparent" {
+ --#{$prefix}btn-active-#{$property}: transparent;
+ } @else if $value == "bg-subtle" {
+ --#{$prefix}btn-active-#{$property}: var(--#{$prefix}theme-#{$value});
+ } @else {
+ --#{$prefix}btn-active-#{$property}: oklch(from var(--#{$prefix}theme-#{$value}) calc(l * .9) calc(c * 1.15) h);
+ }
+ }
+ }
+
+ // Disabled state for toggle buttons
+ &:disabled,
+ &.disabled,
+ &.btn-check:has(input:disabled) {
+ @each $property, $value in map.get($button-variants, $variant, "base") {
+ @if $value == "transparent" {
+ --#{$prefix}btn-disabled-#{$property}: transparent;
+ } @else {
+ --#{$prefix}btn-disabled-#{$property}: var(--#{$prefix}theme-#{$value});
+ }
+ }
+ }
+ }
+ }
+ // scss-docs-end btn-variant-mixin
+
//
// Link buttons
//
// Button Sizes
//
- .btn-lg {
- @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg);
+ // Generate button size classes from the $button-sizes map
+ // Skip "md" as it's the default size for .btn
+ @each $size, $properties in $button-sizes {
+ .btn-#{$size} {
+ --#{$prefix}btn-min-height: #{map.get($properties, "min-height")};
+ --#{$prefix}btn-padding-y: #{map.get($properties, "padding-y")};
+ --#{$prefix}btn-padding-x: #{map.get($properties, "padding-x")};
+ --#{$prefix}btn-font-size: #{map.get($properties, "font-size")};
+ --#{$prefix}btn-line-height: #{map.get($properties, "line-height")};
+ --#{$prefix}btn-border-radius: #{map.get($properties, "border-radius")};
+ }
}
- .btn-sm {
- @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-border-radius-sm);
+ .btn-icon {
+ align-items: center;
+ justify-content: center;
+ aspect-ratio: 1;
+ padding: 0;
}
+ //
+ // Toggle buttons (.btn-check)
+ //
+ // Checkbox and radio inputs that look like buttons. Add .btn-check to a
+ // label with button classes, with the input nested inside.
+ //
+ // Example: <label class="btn-check btn-solid theme-primary"><input type="checkbox">Toggle</label>
+
.btn-check {
- position: absolute;
- clip: rect(0, 0, 0, 0);
- pointer-events: none;
-
- &[disabled],
- &:disabled {
- + .btn {
- pointer-events: none;
- filter: none;
- opacity: .65;
- }
+ > input {
+ position: absolute;
+ clip: rect(0, 0, 0, 0);
+ pointer-events: none;
+ }
+
+ &:has(input:checked) {
+ color: var(--#{$prefix}btn-active-color);
+ background-color: var(--#{$prefix}btn-active-bg, var(--#{$prefix}bg-3));
+ background-image: if($enable-gradients, none, null);
+ border-color: var(--#{$prefix}btn-active-border-color);
+ @include box-shadow(var(--#{$prefix}btn-active-shadow));
+ }
+
+ &:has(input:focus-visible) {
+ @include focus-ring(true);
+ --#{$prefix}focus-ring-offset: 1px;
+ }
+
+ &:has(input:disabled) {
+ color: var(--#{$prefix}btn-disabled-color);
+ pointer-events: none;
+ background-color: var(--#{$prefix}btn-disabled-bg, var(--#{$prefix}bg-1));
+ background-image: if($enable-gradients, none, null);
+ border-color: var(--#{$prefix}btn-disabled-border-color);
+ opacity: var(--#{$prefix}btn-disabled-opacity);
+ @include box-shadow(none);
+ }
+ }
+
+ //
+ // Styled buttons
+ //
+ // Add visual depth with gradients and shadows. Customize via CSS variables.
+
+ .btn-styled {
+ --#{$prefix}btn-gradient-start: rgb(255 255 255 / 12.5%);
+ --#{$prefix}btn-gradient-end: rgb(0 0 0 / 7.5%);
+ --#{$prefix}btn-border-mix-color: #000;
+ --#{$prefix}btn-border-mix-amount: 10%;
+ --#{$prefix}btn-border-hover-mix-amount: 12.5%;
+ --#{$prefix}btn-border-active-mix-amount: 20%;
+ --#{$prefix}btn-shadow: 0 1px 2px rgb(0 0 0 / 15%), inset 0 1px 0 rgb(255 255 255 / 10%);
+ --#{$prefix}btn-active-shadow: inset 0 2px 4px rgba(0, 0, 0, .15);
+
+ background-image:
+ linear-gradient(
+ to bottom,
+ var(--#{$prefix}btn-gradient-start),
+ var(--#{$prefix}btn-gradient-end)
+ );
+ border-color: color-mix(in lab, var(--#{$prefix}theme-bg), var(--#{$prefix}btn-border-mix-color) var(--#{$prefix}btn-border-mix-amount));
+ box-shadow: var(--#{$prefix}btn-shadow);
+
+ &:hover {
+ background-image:
+ linear-gradient(
+ to bottom,
+ var(--#{$prefix}btn-gradient-start),
+ var(--#{$prefix}btn-gradient-end)
+ );
+ border-color: color-mix(in lab, var(--#{$prefix}theme-bg), var(--#{$prefix}btn-border-mix-color) var(--#{$prefix}btn-border-hover-mix-amount));
+ }
+
+ &:active,
+ &.active {
+ background-image: none;
+ border-color: color-mix(in lab, var(--#{$prefix}theme-bg), var(--#{$prefix}btn-border-mix-color) var(--#{$prefix}btn-border-active-mix-amount));
+ box-shadow: var(--#{$prefix}btn-active-shadow);
+ }
+
+ &:disabled,
+ &.disabled {
+ background-image: none;
+ box-shadow: none;
}
}
}
--- /dev/null
+---
+import { getData } from '@libs/data'
+import Example from '@components/shortcodes/Example.astro'
+
+const themeColors = getData('theme-colors')
+const styles = ['solid', 'styled', 'outline', 'subtle', 'text']
+const sizes = [
+ { value: 'xs', label: 'Extra small' },
+ { value: 'sm', label: 'Small' },
+ { value: '', label: 'Medium' },
+ { value: 'lg', label: 'Large' }
+]
+const rounded = ['default', 'pill', 'square']
+---
+
+<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
+ <symbol id="arrow-left" viewBox="0 0 16 16">
+ <path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8"/>
+ </symbol>
+ <symbol id="arrow-right" viewBox="0 0 16 16">
+ <path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8"/>
+ </symbol>
+ <symbol id="plus-lg" viewBox="0 0 16 16">
+ <path fill-rule="evenodd" d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2Z"/>
+ </symbol>
+</svg>
+
+<div class="bg-1 p-3 rounded-3 mb-3">
+ <div class="d-flex flex-wrap gap-3">
+ <div class="vstack gap-1 flex-grow-0">
+ <label class="form-label fw-semibold mb-0">Color</label>
+ <div class="dropdown">
+ <button
+ type="button"
+ class="btn btn-outline theme-secondary dropdown-toggle w-100 d-inline-flex align-items-center justify-content-between"
+ id="btn-color-dropdown"
+ data-bs-toggle="dropdown"
+ aria-expanded="false"
+ data-color="primary"
+ >
+ Primary
+ </button>
+ <ul class="dropdown-menu" aria-labelledby="btn-color-dropdown">
+ {themeColors.map((themeColor) => (
+ <li>
+ <a
+ class:list={['dropdown-item', { 'active': themeColor.name === 'primary' }]}
+ href="#"
+ data-color={themeColor.name}
+ data-selected={themeColor.name === 'primary'}
+ >
+ {themeColor.title}
+ </a>
+ </li>
+ ))}
+ </ul>
+ </div>
+ </div>
+
+ <div class="vstack gap-1 flex-grow-0">
+ <label class="form-label fw-semibold mb-0">Style</label>
+ <div class="btn-group text-capitalize" role="group" aria-label="Button style">
+ {styles.map((style) => (
+ <label class="btn-check btn-outline theme-secondary">
+ <input
+ type="radio"
+ name="btn-style"
+ value={style}
+ checked={style === 'solid'}
+ data-style={style}
+ />
+ {style || 'default'}
+ </label>
+ ))}
+ </div>
+ </div>
+
+ <div class="vstack gap-1 flex-grow-0">
+ <label class="form-label fw-semibold mb-0">Size</label>
+ <div class="dropdown">
+ <button
+ type="button"
+ class="btn btn-outline theme-secondary dropdown-toggle w-100 d-inline-flex align-items-center justify-content-between"
+ id="btn-size-dropdown"
+ data-bs-toggle="dropdown"
+ aria-expanded="false"
+ data-size=""
+ >
+ Medium
+ </button>
+ <ul class="dropdown-menu" aria-labelledby="btn-size-dropdown">
+ {sizes.map((size) => (
+ <li>
+ <a
+ class:list={['dropdown-item', { 'active': size.value === '' }]}
+ href="#"
+ data-size={size.value}
+ data-selected={size.value === ''}
+ >
+ {size.label}
+ </a>
+ </li>
+ ))}
+ </ul>
+ </div>
+ </div>
+
+ <div class="vstack gap-1 flex-grow-0">
+ <label class="form-label fw-semibold mb-0">Rounded</label>
+ <div class="btn-group text-capitalize" role="group" aria-label="Button rounded">
+ {rounded.map((round) => (
+ <label class="btn-check btn-outline theme-secondary">
+ <input
+ type="radio"
+ name="btn-rounded"
+ value={round}
+ checked={round === 'default'}
+ data-rounded={round}
+ />
+ {round}
+ </label>
+ ))}
+ </div>
+ </div>
+ </div>
+</div>
+
+<Example
+ class="d-flex align-items-start gap-2"
+ code={`<button type="button" class="btn btn-solid theme-primary" data-button-type="text">Button</button>
+<button type="button" class="btn btn-solid theme-primary" data-button-type="left-icon">
+ <svg class="bi me-1" width="16" height="16" aria-hidden="true">
+ <use xlink:href="#arrow-left" />
+ </svg>
+ Left icon
+</button>
+<button type="button" class="btn btn-solid theme-primary" data-button-type="right-icon">
+ Right icon
+ <svg class="bi ms-1" width="16" height="16" aria-hidden="true">
+ <use xlink:href="#arrow-right" />
+ </svg>
+</button>
+<button type="button" class="btn btn-solid btn-icon theme-primary" data-button-type="icon-only" aria-label="Icon only">
+ <svg class="bi" width="16" height="16" aria-hidden="true">
+ <use xlink:href="#plus-lg" />
+ </svg>
+</button>`}
+ id="button-preview"
+/>
+
+<script>
+ const colorDropdownButton = document.querySelector('#btn-color-dropdown') as HTMLButtonElement
+ const colorDropdownItems = document.querySelectorAll('#btn-color-dropdown + .dropdown-menu .dropdown-item')
+ const sizeDropdownButton = document.querySelector('#btn-size-dropdown') as HTMLButtonElement
+ const sizeDropdownItems = document.querySelectorAll('#btn-size-dropdown + .dropdown-menu .dropdown-item')
+ const styleInputs = document.querySelectorAll('input[name="btn-style"]')
+ const roundedInputs = document.querySelectorAll('input[name="btn-rounded"]')
+ const previewButtons = document.querySelectorAll('#button-preview button')
+ // Find the code snippet inside the Example component
+ const codeSnippet = document.querySelector('#button-preview')?.closest('.bd-example-snippet')?.querySelector('.highlight code') as HTMLElement
+
+ // Get all theme color names for removal
+ const themeColorNames = Array.from(colorDropdownItems).map(item => (item as HTMLElement).dataset.color || '')
+
+ function generateHTML(button: HTMLElement): string {
+ const classes = Array.from(button.classList).filter(cls =>
+ cls === 'btn' || cls.startsWith('btn-') || cls.startsWith('theme-') || cls.startsWith('rounded')
+ ).join(' ')
+ const buttonType = button.getAttribute('data-button-type')
+ const ariaLabel = button.getAttribute('aria-label')
+
+ let html = `<button type="button" class="${classes}"`
+ if (ariaLabel) {
+ html += ` aria-label="${ariaLabel}"`
+ }
+ html += '>'
+
+ if (buttonType === 'text') {
+ html += 'Click me'
+ } else if (buttonType === 'left-icon') {
+ html += '\n <svg class="bi me-1" width="16" height="16" aria-hidden="true">\n <use xlink:href="#arrow-left" />\n </svg>\n With left icon'
+ } else if (buttonType === 'right-icon') {
+ html += 'With right icon\n <svg class="bi ms-1" width="16" height="16" aria-hidden="true">\n <use xlink:href="#arrow-right" />\n </svg>'
+ } else if (buttonType === 'icon-only') {
+ html += '\n <svg class="bi" width="16" height="16" aria-hidden="true">\n <use xlink:href="#plus-lg" />\n </svg>'
+ }
+
+ html += '\n</button>'
+ return html
+ }
+
+ function updateCodeSnippet() {
+ if (!codeSnippet) return
+
+ const htmlSnippets = Array.from(previewButtons).map(button => generateHTML(button as HTMLElement))
+ const htmlCode = htmlSnippets.join('\n\n')
+
+ // Update the code content
+ codeSnippet.className = 'language-html'
+ codeSnippet.textContent = htmlCode
+
+ // Trigger Prism highlighting if available
+ // Prism from @astrojs/prism is server-side, but we can try to use Prism.js if loaded
+ if (typeof window !== 'undefined' && (window as any).Prism) {
+ (window as any).Prism.highlightElement(codeSnippet)
+ }
+ }
+
+ function updateButtons() {
+ const selectedColor = colorDropdownButton?.dataset.color || 'primary'
+ const selectedStyle = (document.querySelector('input[name="btn-style"]:checked') as HTMLInputElement)?.value || 'solid'
+ const selectedSize = sizeDropdownButton?.dataset.size || ''
+ const selectedRounded = (document.querySelector('input[name="btn-rounded"]:checked') as HTMLInputElement)?.value || 'default'
+
+ previewButtons.forEach((button) => {
+ // Remove all theme color classes
+ themeColorNames.forEach(color => button.classList.remove(`theme-${color}`))
+ // Add selected theme color
+ button.classList.add(`theme-${selectedColor}`)
+
+ // Remove all style classes (including btn-styled)
+ button.classList.remove('btn-solid', 'btn-styled', 'btn-outline', 'btn-subtle', 'btn-text')
+ // Add selected style - styled is btn-solid + btn-styled
+ if (selectedStyle === 'styled') {
+ button.classList.add('btn-solid', 'btn-styled')
+ } else if (selectedStyle) {
+ button.classList.add(`btn-${selectedStyle}`)
+ } else {
+ button.classList.add('btn-solid')
+ }
+
+ // Remove all size classes
+ button.classList.remove('btn-xs', 'btn-sm', 'btn-lg')
+ // Add selected size (only if not empty)
+ if (selectedSize) {
+ button.classList.add(`btn-${selectedSize}`)
+ }
+
+ // Handle rounded - remove all rounded classes first
+ const roundedClasses = ['rounded-pill', 'rounded-0', 'rounded']
+ roundedClasses.forEach(cls => button.classList.remove(cls))
+
+ if (selectedRounded === 'pill') {
+ button.classList.add('rounded-pill')
+ } else if (selectedRounded === 'square') {
+ button.classList.add('rounded-0')
+ }
+ // default uses the default border-radius from CSS variables
+ })
+
+ // Update code snippet
+ updateCodeSnippet()
+ }
+
+ // Initialize Bootstrap dropdowns
+ if (colorDropdownButton) {
+ const colorDropdown = bootstrap.Dropdown.getOrCreateInstance(colorDropdownButton)
+
+ // Handle dropdown item clicks
+ colorDropdownItems.forEach((item) => {
+ item.addEventListener('click', (e) => {
+ e.preventDefault()
+ const colorName = (item as HTMLElement).dataset.color || 'primary'
+ const colorTitle = item.textContent?.trim() || 'Primary'
+
+ // Update button text and data attribute
+ colorDropdownButton.textContent = colorTitle
+ colorDropdownButton.dataset.color = colorName
+
+ // Update selected state
+ colorDropdownItems.forEach((i) => {
+ i.classList.remove('active')
+ i.setAttribute('data-selected', 'false')
+ })
+ item.classList.add('active')
+ item.setAttribute('data-selected', 'true')
+
+ // Hide dropdown
+ colorDropdown.hide()
+
+ // Update buttons
+ updateButtons()
+ })
+ })
+ }
+
+ if (sizeDropdownButton) {
+ const sizeDropdown = bootstrap.Dropdown.getOrCreateInstance(sizeDropdownButton)
+
+ // Handle dropdown item clicks
+ sizeDropdownItems.forEach((item) => {
+ item.addEventListener('click', (e) => {
+ e.preventDefault()
+ const sizeValue = (item as HTMLElement).dataset.size || ''
+ const sizeLabel = item.textContent?.trim() || 'Medium'
+
+ // Update button text and data attribute
+ sizeDropdownButton.textContent = sizeLabel
+ sizeDropdownButton.dataset.size = sizeValue
+
+ // Update selected state
+ sizeDropdownItems.forEach((i) => {
+ i.classList.remove('active')
+ i.setAttribute('data-selected', 'false')
+ })
+ item.classList.add('active')
+ item.setAttribute('data-selected', 'true')
+
+ // Hide dropdown
+ sizeDropdown.hide()
+
+ // Update buttons
+ updateButtons()
+ })
+ })
+ }
+
+ styleInputs.forEach((input) => {
+ input.addEventListener('change', updateButtons)
+ })
+
+ roundedInputs.forEach((input) => {
+ input.addEventListener('change', updateButtons)
+ })
+
+ // Initial update
+ updateButtons()
+ updateCodeSnippet()
+</script>
Combine button-like checkbox and radio toggle buttons into a seamless looking button group.
<Example code={`<div class="btn-group" role="group" aria-label="Basic checkbox toggle button group">
- <input type="checkbox" class="btn-check" id="btncheck1" autocomplete="off">
- <label class="btn btn-outline-primary" for="btncheck1">Checkbox 1</label>
-
- <input type="checkbox" class="btn-check" id="btncheck2" autocomplete="off">
- <label class="btn btn-outline-primary" for="btncheck2">Checkbox 2</label>
-
- <input type="checkbox" class="btn-check" id="btncheck3" autocomplete="off">
- <label class="btn btn-outline-primary" for="btncheck3">Checkbox 3</label>
+ <label class="btn-check btn-outline theme-primary">
+ <input type="checkbox" autocomplete="off">
+ Checkbox 1
+ </label>
+ <label class="btn-check btn-outline theme-primary">
+ <input type="checkbox" autocomplete="off">
+ Checkbox 2
+ </label>
+ <label class="btn-check btn-outline theme-primary">
+ <input type="checkbox" autocomplete="off">
+ Checkbox 3
+ </label>
</div>`} />
<Example code={`<div class="btn-group" role="group" aria-label="Basic radio toggle button group">
- <input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" checked>
- <label class="btn btn-outline-primary" for="btnradio1">Radio 1</label>
-
- <input type="radio" class="btn-check" name="btnradio" id="btnradio2" autocomplete="off">
- <label class="btn btn-outline-primary" for="btnradio2">Radio 2</label>
-
- <input type="radio" class="btn-check" name="btnradio" id="btnradio3" autocomplete="off">
- <label class="btn btn-outline-primary" for="btnradio3">Radio 3</label>
+ <label class="btn-check btn-outline theme-primary">
+ <input type="radio" name="btnradio" autocomplete="off" checked>
+ Radio 1
+ </label>
+ <label class="btn-check btn-outline theme-primary">
+ <input type="radio" name="btnradio" autocomplete="off">
+ Radio 2
+ </label>
+ <label class="btn-check btn-outline theme-primary">
+ <input type="radio" name="btnradio" autocomplete="off">
+ Radio 3
+ </label>
</div>`} />
## Button toolbar
</div>`} />
<Example code={`<div class="btn-group-vertical" role="group" aria-label="Vertical radio toggle button group">
- <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio1" autocomplete="off" checked>
- <label class="btn btn-outline-danger" for="vbtn-radio1">Radio 1</label>
- <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio2" autocomplete="off">
- <label class="btn btn-outline-danger" for="vbtn-radio2">Radio 2</label>
- <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio3" autocomplete="off">
- <label class="btn btn-outline-danger" for="vbtn-radio3">Radio 3</label>
+ <label class="btn-check btn-outline theme-danger">
+ <input type="radio" name="vbtn-radio" autocomplete="off" checked>
+ Radio 1
+ </label>
+ <label class="btn-check btn-outline theme-danger">
+ <input type="radio" name="vbtn-radio" autocomplete="off">
+ Radio 2
+ </label>
+ <label class="btn-check btn-outline theme-danger">
+ <input type="radio" name="vbtn-radio" autocomplete="off">
+ Radio 3
+ </label>
</div>`} />
## Base class
-Bootstrap has a base `.btn` class that sets up basic styles such as padding and content alignment. By default, `.btn` controls have a transparent border and background color, and lack any explicit focus and hover styles.
+Buttons have a standard `.btn` class that sets up basic styles such as padding and content alignment, along with fallback visual styles for basic accessibility. Change this class to another button class to apply different visual styles for a specific button variant and theme.
<Example code={`<button type="button" class="btn">Base class</button>`} />
-The `.btn` class is intended to be used in conjunction with our button variants, or to serve as a basis for your own custom styles.
+The `.btn` class is intended to be a starting point for your own custom button styles, while our provided button variants used in conjunction with our button variants, or to serve as a basis for your own custom styles.
<Callout type="warning">
When using `.btn` without a modifier, be sure to add some explicit `:focus-visible` styles.
</Callout>
+## Playground
+
+Bootstrap includes several button variants, each serving its own semantic purpose, with a few extras thrown in for more control. Combined with our utilities, you have a very powerful set of defaults to choose from.
+
+<ButtonPlayground />
+
## Variants
-Bootstrap includes several button variants, each serving its own semantic purpose, with a few extras thrown in for more control.
+Compare all variants at once:
<Example class="bd-example-buttons" code={[...getData('theme-colors').map((themeColor) => `<button type="button" class="btn-solid theme-${themeColor.name} justify-self-start">${themeColor.title}</button>
<button type="button" class="btn-outline theme-${themeColor.name} justify-self-start">${themeColor.title}</button>
<Details name="warning-color-assistive-technologies" />
+## Styled buttons
+
+Add visual depth to solid buttons with gradients and shadows using the `.btn-styled` modifier class. This provides a more three-dimensional appearance that can be customized further with CSS variables.
+
+<Example code={[...getData('theme-colors').map((themeColor) => `<button class="btn-solid btn-styled theme-${themeColor.name}">${themeColor.title}</button>`)]} />
+
+The gradient and shadow can be customized via CSS variables:
+
+<BsTable>
+| Variable | Description |
+| --- | --- |
+| `--bs-btn-gradient-start` | Top of gradient (default: `rgb(255 255 255 / 12.5%)`) |
+| `--bs-btn-gradient-end` | Bottom of gradient (default: `rgb(0 0 0 / 7.5%)`) |
+| `--bs-btn-border-mix-color` | Color to mix with the button background (default: `#000`) |
+| `--bs-btn-border-mix-amount` | Amount of color to mix with the button background (default: `10%`) |
+| `--bs-btn-border-hover-mix-amount` | Amount of color to mix with the button background on hover (default: `12.5%`) |
+| `--bs-btn-border-active-mix-amount` | Amount of color to mix with the button background on active (default: `20%`) |
+| `--bs-btn-shadow` | Resting state shadow |
+| `--bs-btn-active-shadow` | Pressed/active state shadow |
+</BsTable>
+
## Disable text wrapping
If you don’t want the button text to wrap, you can add the `.text-nowrap` class to the button. In Sass, you can set `$btn-white-space: nowrap` to disable text wrapping for each button.
When using button classes on `<a>` elements that are used to trigger in-page functionality (like collapsing content), rather than linking to new pages or sections within the current page, these links should be given a `role="button"` to appropriately convey their purpose to assistive technologies such as screen readers.
-<Example code={`<a class="btn btn-primary" href="#" role="button">Link</a>
-<button class="btn btn-primary" type="submit">Button</button>
-<input class="btn btn-primary" type="button" value="Input">
-<input class="btn btn-primary" type="submit" value="Submit">
-<input class="btn btn-primary" type="reset" value="Reset">`} />
-
-## Outline buttons
-
-In need of a button, but not the hefty background colors they bring? Replace the default modifier classes with the `.btn-outline-*` ones to remove all background images and colors on any button.
-
-<Example code={getData('theme-colors').map((themeColor) => `<button type="button" class="btn btn-outline-${themeColor.name}">${themeColor.title}</button>`)} />
-
-<Callout>
-Some of the button styles use a relatively light foreground color, and should only be used on a dark background in order to have sufficient contrast.
-</Callout>
+<Example code={`<a class="btn-solid theme-primary" href="#" role="button">Link</a>
+<button class="btn-solid theme-primary" type="submit">Button</button>
+<input class="btn-solid theme-primary" type="button" value="Input">
+<input class="btn-solid theme-primary" type="submit" value="Submit">
+<input class="btn-solid theme-primary" type="reset" value="Reset">`} />
## Sizes
Fancy larger or smaller buttons? Add `.btn-lg` or `.btn-sm` for additional sizes.
-<Example code={`<button type="button" class="btn btn-primary btn-lg">Large button</button>
-<button type="button" class="btn btn-secondary btn-lg">Large button</button>`} />
+<Example code={`<button type="button" class="btn-solid theme-primary btn-lg">Large button</button>
+<button type="button" class="btn-solid theme-secondary btn-lg">Large button</button>`} />
+
+<Example code={`<button type="button" class="btn-solid theme-primary btn-sm">Small button</button>
+<button type="button" class="btn-solid theme-secondary btn-sm">Small button</button>`} />
-<Example code={`<button type="button" class="btn btn-primary btn-sm">Small button</button>
-<button type="button" class="btn btn-secondary btn-sm">Small button</button>`} />
+<Example code={`<button type="button" class="btn-solid theme-primary btn-xs">Extra small button</button>
+<button type="button" class="btn-solid theme-secondary btn-xs">Extra small button</button>`} />
You can even roll your own custom sizing with CSS variables:
-<Example code={`<button type="button" class="btn btn-primary"
+<Example code={`<button type="button" class="btn-solid theme-primary"
style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;">
Custom button
</button>`} />
+## Icon buttons
+
+Use `.btn-icon` for square buttons that contain only an icon. This class sets `aspect-ratio: 1` and removes padding, making the button perfectly square based on its `min-height`. Always include an `aria-label` attribute on icon-only buttons to ensure they are accessible to screen reader users.
+
+<Example code={`<button type="button" class="btn-solid theme-primary btn-icon" aria-label="Confirm">
+ <svg class="bi" width="16" height="16" fill="currentColor" aria-hidden="true"><use href="#check2"></use></svg>
+ </button>
+ <button type="button" class="btn-outline theme-secondary btn-icon" aria-label="Auto theme">
+ <svg class="bi" width="16" height="16" fill="currentColor" aria-hidden="true"><use href="#circle-half"></use></svg>
+ </button>
+ <button type="button" class="btn-subtle theme-warning btn-icon" aria-label="Light theme">
+ <svg class="bi" width="16" height="16" fill="currentColor" aria-hidden="true"><use href="#sun-fill"></use></svg>
+ </button>`} />
+
+Combine with size classes for different icon button sizes:
+
+<Example code={`<button type="button" class="btn-solid theme-primary btn-icon btn-xs" aria-label="Confirm">
+ <svg class="bi" width="12" height="12" fill="currentColor" aria-hidden="true"><use href="#check2"></use></svg>
+ </button>
+ <button type="button" class="btn-solid theme-primary btn-icon btn-sm" aria-label="Confirm">
+ <svg class="bi" width="14" height="14" fill="currentColor" aria-hidden="true"><use href="#check2"></use></svg>
+ </button>
+ <button type="button" class="btn-solid theme-primary btn-icon" aria-label="Confirm">
+ <svg class="bi" width="16" height="16" fill="currentColor" aria-hidden="true"><use href="#check2"></use></svg>
+ </button>
+ <button type="button" class="btn-solid theme-primary btn-icon btn-lg" aria-label="Confirm">
+ <svg class="bi" width="20" height="20" fill="currentColor" aria-hidden="true"><use href="#check2"></use></svg>
+ </button>`} />
+
## Disabled state
-Make buttons look inactive by adding the `disabled` boolean attribute to any `<button>` element. Disabled buttons have `pointer-events: none` applied to, preventing hover and active states from triggering.
+Make buttons look inactive by adding the `disabled` boolean attribute to any `<button>` element. Disabled buttons have `pointer-events: none` applied to, preventing hover and active states from triggering, and some additional styling to indicate the disabled state.
-<Example code={`<button type="button" class="btn btn-primary" disabled>Primary button</button>
-<button type="button" class="btn btn-secondary" disabled>Button</button>
-<button type="button" class="btn btn-outline-primary" disabled>Primary button</button>
-<button type="button" class="btn btn-outline-secondary" disabled>Button</button>`} />
+<Example code={`<button type="button" class="btn-solid theme-primary" disabled>Primary button</button>
+<button type="button" class="btn-solid theme-secondary" disabled>Button</button>
+<button type="button" class="btn-outline theme-primary" disabled>Primary button</button>
+<button type="button" class="btn-outline theme-secondary" disabled>Button</button>`} />
-Disabled buttons using the `<a>` element behave a bit different:
+### Disabled links
-- `<a>`s don’t support the `disabled` attribute, so you must add the `.disabled` class to make it visually appear disabled.
-- Some future-friendly styles are included to disable all `pointer-events` on anchor buttons.
-- Disabled buttons using `<a>` should include the `aria-disabled="true"` attribute to indicate the state of the element to assistive technologies.
-- Disabled buttons using `<a>` *should not* include the `href` attribute.
+Disabled buttons using the `<a>` element behave a bit different, and if you need to keep the `href` attribute, you may need additional HTML to fully disable the link functionality. Use the `.disabled` class instead of the attribute on `<a>` elements to make them visually appear disabled.
-<Example code={`<a class="btn btn-primary disabled" role="button" aria-disabled="true">Primary link</a>
-<a class="btn btn-secondary disabled" role="button" aria-disabled="true">Link</a>`} />
+<Example code={`<a class="btn-solid theme-primary disabled" role="button" aria-disabled="true">Primary link</a>
+<a class="btn-solid theme-secondary disabled" role="button" aria-disabled="true">Link</a>`} />
-### Link functionality caveat
+If there's an `href` attribute, add `tabindex="-1"` and `aria-disabled="true"` to prevent keyboard focus and assistive technologies from interacting with the link.
-To cover cases where you have to keep the `href` attribute on a disabled link, the `.disabled` class uses `pointer-events: none` to try to disable the link functionality of `<a>`s. Note that this CSS property is not yet standardized for HTML, but all modern browsers support it. In addition, even in browsers that do support `pointer-events: none`, keyboard navigation remains unaffected, meaning that sighted keyboard users and users of assistive technologies will still be able to activate these links. So to be safe, in addition to `aria-disabled="true"`, also include a `tabindex="-1"` attribute on these links to prevent them from receiving keyboard focus, and use custom JavaScript to disable their functionality altogether.
+<Example code={`<a href="#" class="btn-solid theme-primary disabled" tabindex="-1" role="button" aria-disabled="true">Primary link</a>
+<a href="#" class="btn-solid theme-secondary disabled" tabindex="-1" role="button" aria-disabled="true">Link</a>`} />
-<Example code={`<a href="#" class="btn btn-primary disabled" tabindex="-1" role="button" aria-disabled="true">Primary link</a>
-<a href="#" class="btn btn-secondary disabled" tabindex="-1" role="button" aria-disabled="true">Link</a>`} />
+You may also want to use custom JavaScript to disable the link functionality altogether.
## Block buttons
-Create responsive stacks of full-width, “block buttons” like those in Bootstrap 4 with a mix of our display and gap utilities. By using utilities instead of button-specific classes, we have much greater control over spacing, alignment, and responsive behaviors.
+Use the `.d-grid` utility to create responsive stacks of full-width “block buttons”. Space them out with gap utilities.
<Example code={`<div class="d-grid gap-2">
- <button class="btn btn-primary" type="button">Button</button>
- <button class="btn btn-primary" type="button">Button</button>
+ <button class="btn-solid theme-primary" type="button">Button</button>
+ <button class="btn-solid theme-primary" type="button">Button</button>
</div>`} />
Here we create a responsive variation, starting with vertically stacked buttons until the `md` breakpoint, where `.d-md-block` replaces the `.d-grid` class, thus nullifying the `gap-2` utility. Resize your browser to see them change.
<Example code={`<div class="d-grid gap-2 d-md-block">
- <button class="btn btn-primary" type="button">Button</button>
- <button class="btn btn-primary" type="button">Button</button>
+ <button class="btn-solid theme-primary" type="button">Button</button>
+ <button class="btn-solid theme-primary" type="button">Button</button>
</div>`} />
You can adjust the width of your block buttons with grid column width classes. For example, for a half-width “block button”, use `.col-6`. Center it horizontally with `.mx-auto`, too.
<Example code={`<div class="d-grid gap-2 col-6 mx-auto">
- <button class="btn btn-primary" type="button">Button</button>
- <button class="btn btn-primary" type="button">Button</button>
+ <button class="btn-solid theme-primary" type="button">Button</button>
+ <button class="btn-solid theme-primary" type="button">Button</button>
</div>`} />
Additional utilities can be used to adjust the alignment of buttons when horizontal. Here we’ve taken our previous responsive example and added some flex utilities and a margin utility on the button to right-align the buttons when they’re no longer stacked.
<Example code={`<div class="d-grid gap-2 d-md-flex justify-content-md-end">
- <button class="btn btn-primary me-md-2" type="button">Button</button>
- <button class="btn btn-primary" type="button">Button</button>
+ <button class="btn-solid theme-primary me-md-2" type="button">Button</button>
+ <button class="btn-solid theme-primary" type="button">Button</button>
</div>`} />
## Toggle buttons
-Create button-like checkboxes and radio buttons by using `.btn` styles rather than `.form-check-label` on the `<label>` elements. These toggle buttons can further be grouped in a [button group]([[docsref:/components/button-group]]) if needed.
-
-### Checkbox toggle buttons
-
-<Example code={`<input type="checkbox" class="btn-check" id="btn-check" autocomplete="off">
-<label class="btn btn-primary" for="btn-check">Single toggle</label>
-
-<input type="checkbox" class="btn-check" id="btn-check-2" checked autocomplete="off">
-<label class="btn btn-primary" for="btn-check-2">Checked</label>
-
-<input type="checkbox" class="btn-check" id="btn-check-3" autocomplete="off" disabled>
-<label class="btn btn-primary" for="btn-check-3">Disabled</label>`} />
-
-<Example code={`<input type="checkbox" class="btn-check" id="btn-check-4" autocomplete="off">
-<label class="btn" for="btn-check-4">Single toggle</label>
-
-<input type="checkbox" class="btn-check" id="btn-check-5" checked autocomplete="off">
-<label class="btn" for="btn-check-5">Checked</label>
-
-<input type="checkbox" class="btn-check" id="btn-check-6" autocomplete="off" disabled>
-<label class="btn" for="btn-check-6">Disabled</label>`} />
+Create button-like checkboxes and radio buttons by adding `.btn-check` to a `<label>` element with button classes, with a checkbox or radio input nested inside. The input is visually hidden while the label provides the button appearance. These toggle buttons can further be grouped in a [button group]([[docsref:/components/button-group]]) if needed.
<Callout>
-Visually, these checkbox toggle buttons are identical to the [button plugin toggle buttons]([[docsref:/components/buttons#button-plugin]]). However, they are conveyed differently by assistive technologies: the checkbox toggles will be announced by screen readers as “checked“/“not checked“ (since, despite their appearance, they are fundamentally still checkboxes), whereas the button plugin toggle buttons will be announced as “button“/“button pressed“. The choice between these two approaches will depend on the type of toggle you are creating, and whether or not the toggle will make sense to users when announced as a checkbox or as an actual button.
+Visually, these toggle buttons are identical to our [JavaScript toggle buttons](#button-plugin), but are conveyed differently by assistive technologies. Checkbox toggles will be announced by screen readers as "checked"/"not checked"as they are checkboxes under the hood. The JavaScript toggle buttons are announced as "button"/"button pressed". It’s up to you to decide which approach makes more sense for your use case.
</Callout>
-### Radio toggle buttons
-
-<Example code={`<input type="radio" class="btn-check" name="options" id="option1" autocomplete="off" checked>
-<label class="btn btn-secondary" for="option1">Checked</label>
-
-<input type="radio" class="btn-check" name="options" id="option2" autocomplete="off">
-<label class="btn btn-secondary" for="option2">Radio</label>
+### Checkboxes
-<input type="radio" class="btn-check" name="options" id="option3" autocomplete="off" disabled>
-<label class="btn btn-secondary" for="option3">Disabled</label>
+Checkboxes can be checked, unchecked, or disabled. Checkbox-based toggle buttons only toggle a single button’s state.
-<input type="radio" class="btn-check" name="options" id="option4" autocomplete="off">
-<label class="btn btn-secondary" for="option4">Radio</label>`} />
+<Example code={`<label class="btn-check btn-solid theme-primary">
+ <input type="checkbox" checked autocomplete="off">
+ Checked
+ </label>
-<Example code={`<input type="radio" class="btn-check" name="options-base" id="option5" autocomplete="off" checked>
-<label class="btn" for="option5">Checked</label>
+ <label class="btn-check btn-solid theme-primary">
+ <input type="checkbox" autocomplete="off">
+ Toggle
+ </label>
-<input type="radio" class="btn-check" name="options-base" id="option6" autocomplete="off">
-<label class="btn" for="option6">Radio</label>
+ <label class="btn-check btn-solid theme-primary">
+ <input type="checkbox" disabled autocomplete="off">
+ Disabled
+ </label>`} />
-<input type="radio" class="btn-check" name="options-base" id="option7" autocomplete="off" disabled>
-<label class="btn" for="option7">Disabled</label>
+### Radios
-<input type="radio" class="btn-check" name="options-base" id="option8" autocomplete="off">
-<label class="btn" for="option8">Radio</label>`} />
+Radios can be checked, unchecked, or disabled. Use radio-based toggle buttons to toggle states between a set of buttons that include input elements with the same `name` attribute value.
-### Outlined styles
+<Example code={`<label class="btn-check btn-solid theme-primary">
+ <input type="radio" name="options" autocomplete="off" checked>
+ Checked
+ </label>
-Different variants of `.btn`, such as the various outlined styles, are supported.
+ <label class="btn-check btn-solid theme-primary">
+ <input type="radio" name="options" autocomplete="off">
+ Toggle
+ </label>
-<Example code={`<input type="checkbox" class="btn-check" id="btn-check-outlined" autocomplete="off">
-<label class="btn btn-outline-primary" for="btn-check-outlined">Single toggle</label><br>
+ <label class="btn-check btn-solid theme-primary">
+ <input type="radio" name="options" disabled autocomplete="off">
+ Disabled
+ </label>
-<input type="checkbox" class="btn-check" id="btn-check-2-outlined" checked autocomplete="off">
-<label class="btn btn-outline-secondary" for="btn-check-2-outlined">Checked</label><br>
-
-<input type="radio" class="btn-check" name="options-outlined" id="success-outlined" autocomplete="off" checked>
-<label class="btn btn-outline-success" for="success-outlined">Checked success radio</label>
-
-<input type="radio" class="btn-check" name="options-outlined" id="danger-outlined" autocomplete="off">
-<label class="btn btn-outline-danger" for="danger-outlined">Danger radio</label>`} />
+ <label class="btn-check btn-solid theme-primary">
+ <input type="radio" name="options" autocomplete="off">
+ Toggle
+ </label>`} />
## Button plugin
The button plugin allows you to create simple on/off toggle buttons.
<Callout>
-Visually, these toggle buttons are identical to the [checkbox toggle buttons]([[docsref:/forms/checkbox]]). However, they are conveyed differently by assistive technologies: the checkbox toggles will be announced by screen readers as “checked”/“not checked” (since, despite their appearance, they are fundamentally still checkboxes), whereas these toggle buttons will be announced as “button”/“button pressed”. The choice between these two approaches will depend on the type of toggle you are creating, and whether or not the toggle will make sense to users when announced as a checkbox or as an actual button.
+Visually, these toggle buttons are identical to our [input toggle buttons](#toggle-buttons), but are conveyed differently by assistive technologies. Checkbox toggles will be announced by screen readers as "checked"/"not checked"as they are checkboxes under the hood. The JavaScript toggle buttons are announced as "button"/"button pressed". It’s up to you to decide which approach makes more sense for your use case.
</Callout>
### Toggle states
Add `data-bs-toggle="button"` to toggle a button’s `active` state. If you’re pre-toggling a button, you must manually add the `.active` class **and** `aria-pressed="true"` to ensure that it is conveyed appropriately to assistive technologies.
-<Example code={`<p class="d-inline-flex gap-1">
- <button type="button" class="btn" data-bs-toggle="button">Toggle button</button>
- <button type="button" class="btn active" data-bs-toggle="button" aria-pressed="true">Active toggle button</button>
- <button type="button" class="btn" disabled data-bs-toggle="button">Disabled toggle button</button>
- </p>
- <p class="d-inline-flex gap-1">
- <button type="button" class="btn btn-primary" data-bs-toggle="button">Toggle button</button>
- <button type="button" class="btn btn-primary active" data-bs-toggle="button" aria-pressed="true">Active toggle button</button>
- <button type="button" class="btn btn-primary" disabled data-bs-toggle="button">Disabled toggle button</button>
- </p>`} />
-
-<Example code={`<p class="d-inline-flex gap-1">
- <a href="#" class="btn" role="button" data-bs-toggle="button">Toggle link</a>
- <a href="#" class="btn active" role="button" data-bs-toggle="button" aria-pressed="true">Active toggle link</a>
- <a class="btn disabled" aria-disabled="true" role="button" data-bs-toggle="button">Disabled toggle link</a>
- </p>
- <p class="d-inline-flex gap-1">
- <a href="#" class="btn btn-primary" role="button" data-bs-toggle="button">Toggle link</a>
- <a href="#" class="btn btn-primary active" role="button" data-bs-toggle="button" aria-pressed="true">Active toggle link</a>
- <a class="btn btn-primary disabled" aria-disabled="true" role="button" data-bs-toggle="button">Disabled toggle link</a>
- </p>`} />
+<Example class="d-inline-flex gap-1" code={`<button type="button" class="btn-solid theme-primary" data-bs-toggle="button">Toggle button</button>
+ <button type="button" class="btn-solid theme-primary active" data-bs-toggle="button" aria-pressed="true">Active toggle button</button>
+ <button type="button" class="btn-solid theme-primary" disabled data-bs-toggle="button">Disabled toggle button</button>`} />
+
+<Example class="d-inline-flex gap-1" code={`<a href="#" class="btn-solid theme-primary" role="button" data-bs-toggle="button">Toggle link</a>
+ <a href="#" class="btn-solid theme-primary active" role="button" data-bs-toggle="button" aria-pressed="true">Active toggle link</a>
+ <a class="btn-solid theme-primary disabled" aria-disabled="true" role="button" data-bs-toggle="button">Disabled toggle link</a>`} />
### Methods
### Variables
-<CSSVariables component="Buttons" className="btn" />
+Buttons use CSS variables for real-time customization. These are set on the base `.btn` class and inherited by all button variants.
<ScssDocs name="btn-css-vars" file="scss/buttons/_button.scss" />
-Each `.btn-*` modifier class updates the appropriate CSS variables to minimize additional CSS rules with our `button-variant()`, `button-outline-variant()`, and `button-size()` mixins.
-
-Here’s an example of building a custom `.btn-*` modifier class as we do for the buttons unique to our docs by reassigning Bootstrap’s CSS variables with a mixture of our own CSS and Sass variables.
-
-<Example showMarkup={false} code={`
-<button type="button" class="btn btn-bd-primary">Custom button</button>
-`} />
-
-<ScssDocs name="btn-css-vars-example" file="site/src/scss/_buttons.scss" />
+Each variant class (`.btn-solid`, `.btn-outline`, etc.) updates these variables based on the current theme color, allowing buttons to adapt automatically when used with `.theme-*` classes.
### Sass variables
+Base button styling uses these Sass variables for padding, typography, and sizing across all button variants.
+
<ScssDocs name="btn-variables" file="scss/buttons/_button-variables.scss" />
-### Sass map
+### Sass maps
-Button variants—including all their states—are defined in the `$button-variants` Sass map. This map identifies which theme color tokens to use for each variant's state.
+#### Variants map
-For example, a solid button uses the same `bg` token for its background and border colors because we want it to have a seamless look.
+Button variants are defined in the `$button-variants` Sass map. Each variant specifies which theme color tokens to use for its base, hover, and active states. The map uses token names (like `"bg"`, `"contrast"`, `"border"`) that reference the current theme's color palette.
<ScssDocs name="btn-variants" file="scss/buttons/_button.scss" />
-### Sass mixins
+To add a custom variant, extend the map before importing Bootstrap’s buttons:
+
+```scss
+@use "bootstrap/scss/buttons/button" with (
+ $button-variants: (
+ "solid": ( /* ... */ ),
+ "outline": ( /* ... */ ),
+ "subtle": ( /* ... */ ),
+ "text": ( /* ... */ ),
+ "ghost": (
+ "base": (
+ "bg": "transparent",
+ "color": "text",
+ "border-color": "transparent"
+ ),
+ "hover": (
+ "bg": "bg-subtle",
+ "color": "text-emphasis"
+ ),
+ "active": (
+ "bg": "bg-subtle",
+ "color": "text-emphasis"
+ )
+ )
+ )
+);
+```
-There are three mixins for buttons: button and button outline variant mixins (both based on `$theme-colors`), plus a button size mixin.
+To remove a variant you don't need, use Sass's `map.remove()`:
-<ScssDocs name="btn-variant-mixin" file="scss/buttons/_button.scss" />
+```scss
+@use "sass:map";
-{/*<ScssDocs name="btn-outline-variant-mixin" file="scss/mixins/_buttons.scss" />
+@use "bootstrap/scss/buttons/button" as *button* with (
+ $button-variants: map.remove($button-variants, "text", "subtle")
+);
+```
-<ScssDocs name="btn-size-mixin" file="scss/mixins/_buttons.scss" />*/}
+#### Sizes map
+
+Button sizes are defined in the `$button-sizes` map. Each size specifies padding, font size, line height, border radius, and minimum height. The loop generates `.btn-xs`, `.btn-sm`, and `.btn-lg` classes.
+
+<ScssDocs name="btn-sizes" file="scss/buttons/_button.scss" />
+
+To add or modify sizes, extend the map:
+
+```scss
+@use "bootstrap/scss/buttons/button" with (
+ $button-sizes: (
+ "xs": ( /* ... */ ),
+ "sm": ( /* ... */ ),
+ "lg": ( /* ... */ ),
+ "xl": (
+ "padding-y": .75rem,
+ "padding-x": 1.25rem,
+ "font-size": var(--bs-font-size-lg),
+ "line-height": 1.5rem,
+ "border-radius": var(--bs-border-radius-lg),
+ "min-height": 3.25rem
+ )
+ )
+);
+```
### Sass loops
-Button variants (for regular and outline buttons) use their respective mixins with our `$theme-colors` map to generate the modifier classes in `scss/_buttons.scss`.
+#### Variant loop
+
+The variant loop iterates over `$button-variants` to generate each variant class. It sets CSS variables for base, hover, active, and disabled states using the theme color tokens defined in the map.
-{/* <ScssDocs name="btn-variant-loops" file="scss/buttons/_button.scss" /> */}
+<ScssDocs name="btn-variant-mixin" file="scss/buttons/_button.scss" />
+
+#### Size loop
+
+The size loop iterates over `$button-sizes` to generate size modifier classes, setting the appropriate CSS variables for each size.
+
+```scss
+@each $size, $properties in $button-sizes {
+ .btn-#{$size} {
+ --bs-btn-min-height: #{map.get($properties, "min-height")};
+ --bs-btn-padding-y: #{map.get($properties, "padding-y")};
+ --bs-btn-padding-x: #{map.get($properties, "padding-x")};
+ --bs-btn-font-size: #{map.get($properties, "font-size")};
+ --bs-btn-line-height: #{map.get($properties, "line-height")};
+ --bs-btn-border-radius: #{map.get($properties, "border-radius")};
+ }
+}
+```
## Guiding principles
-Beyond what Bootstrap does, here's _why_ we do it—our philosophy for building on the web. At a high level, here's what guides our approach:
+Beyond what Bootstrap does, here's *why* we do it—our philosophy for building on the web. At a high level, here's what guides our approach:
- Components should be responsive and mobile-first
- Components should be built with a base class and extended via modifier classes
Some components in Bootstrap are built with overlapping elements to prevent double borders without modifying the `border` property. For example, button groups, input groups, and pagination. These components share a standard `z-index` scale of `0` through `3`, matching our expectations of highest user priority.
- `0` is for default states (initial, not actually set)
-- `1` is for `:hover`, lowest because while it indicates user intent, nearly _anything_ can be hovered.
+- `1` is for `:hover`, lowest because while it indicates user intent, nearly *anything* can be hovered.
- `2` is for `:active`/`.active`, second highest because they indicate state.
- `3` is for `:focus`, highest because focused elements are in view and at the user’s attention.
</html>
```
-2. **Include Bootstrap’s CSS and JS.** Place the `<link>` tag in the `<head>` for our CSS, and the `<script>` tag for our JavaScript bundle (including Popper for positioning dropdowns, popovers, and tooltips) before the closing `</body>`. Learn more about our [CDN links](#cdn-links).
+2. **Include Bootstrap’s CSS and JS.** Place the `<link>` tag in the `<head>` for our CSS, and the `<script>` tag for our JavaScript bundle (including Popper for positioning dropdowns, popovers, and tooltips) before the closing `</body>`. Learn more about [our CDN links]([[docsref:/getting-started/install#cdn]]).
```html
<!doctype html>
- Removed several now unused Sass variables.
- Checkboxes now have a wrapping element and an SVG in the DOM for checked and indeterminate states. Radios and switches do not.
- Revamped layout for checks, radios, and switches with labels (and descriptions). We now have custom elements for layout that include basic flexbox styling.
- - @mdo-do: Decide on fate of `.form-check-reverse` and `.btn-check`
+ - Refactored toggle buttons to use a nested input structure. The `.btn-check` class now goes on the label (not the input), with the input nested inside. This eliminates the need for `id`/`for` attributes and uses CSS `:has()` selector instead of sibling selectors. Example: `<label class="btn-check btn-solid theme-primary"><input type="checkbox">Toggle</label>`.
- **Consolidate `.form-select` into `.form-control`.**
- Removed `.form-select`—use `.form-control` on `<select>` elements now. Too much abstraction and duplication at the same time.
- Adds new CSS variables on `.form-control` for easier customization without Sass compilation.
}
// Buttons
- > .btn,
> .btn-group {
margin: .25rem .125rem;
}
a {
white-space: nowrap;
}
-
- .btn {
- margin: .25rem .125rem;
- }
}
// scss-docs-start custom-tooltip
@layer custom {
.bd-details {
margin-block: 1.25rem;
+ font-size: var(--bs-font-size-sm);
+ line-height: var(--bs-line-height-sm);
color: var(--bs-fg-3);
background-color: var(--bs-bg-1);
@include border-radius(var(--bs-border-radius-lg));
*/
export declare global {
export const BsTable: typeof import('@shortcodes/BsTable.astro').default
+ export const ButtonPlayground: typeof import('@shortcodes/ButtonPlayground.astro').default
export const CSSVariables: typeof import('@shortcodes/CSSVariables.astro').default
export const Callout: typeof import('@shortcodes/Callout.astro').default
export const CalloutDeprecatedDarkVariants: typeof import('@shortcodes/CalloutDeprecatedDarkVariants.astro').default