]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
New form controls
authorMark Otto <markdotto@gmail.com>
Wed, 3 Sep 2025 03:59:19 +0000 (20:59 -0700)
committerMark Otto <markdotto@gmail.com>
Wed, 24 Sep 2025 06:20:58 +0000 (23:20 -0700)
21 files changed:
.bundlewatch.config.json
.cspell.json
scss/_root.scss
scss/_variables.scss
scss/content/_reboot.scss
scss/forms/_form-check.scss
scss/forms/_form-control.scss
scss/forms/_form-select.scss [deleted file]
scss/forms/_form-variables.scss
scss/forms/_input-group.scss
scss/forms/_switch.scss [new file with mode: 0644]
scss/forms/index.scss
scss/helpers/_stacks.scss
site/data/sidebar.yml
site/src/content/docs/components/buttons.mdx
site/src/content/docs/customize/overview.mdx
site/src/content/docs/forms/checkbox.mdx [new file with mode: 0644]
site/src/content/docs/forms/form-control.mdx
site/src/content/docs/forms/radio.mdx [new file with mode: 0644]
site/src/content/docs/forms/select.mdx [deleted file]
site/src/content/docs/forms/switch.mdx [new file with mode: 0644]

index 0177b5f615c9bbad11d6fed8b8739f7edf7b78ed..7d0f30525e49ab7037f2734654e3b68375f25166 100644 (file)
     },
     {
       "path": "./dist/css/bootstrap.css",
-      "maxSize": "35.75 kB"
+      "maxSize": "36.0 kB"
     },
     {
       "path": "./dist/css/bootstrap.min.css",
-      "maxSize": "31.5 kB"
+      "maxSize": "32.0 kB"
     },
     {
       "path": "./dist/js/bootstrap.bundle.js",
index 191c9bc05c2ff88e6dda15e74e0052572723b8ec..872c34b23c39ec66bb72cce0c4477b115d0f004a 100644 (file)
@@ -21,6 +21,7 @@
     "callout",
     "callouts",
     "camelCase",
+    "checkgroup",
     "clearfix",
     "Codesniffer",
     "combinator",
index 241147aa6526d71a57a1d5f543b41bb2f4159ba4..478114f95a55967e1ab1731f856f171d0afd8a25 100644 (file)
     --#{$prefix}root-font-size: #{$font-size-root};
   }
   --#{$prefix}body-font-family: #{meta.inspect($font-family-base)};
+
+  --#{$prefix}font-size-base: #{$font-size-base}; // 14px
+  --#{$prefix}font-size-sm: calc(#{$font-size-base} * .9285);
+  --#{$prefix}font-size-lg: calc(#{$font-size-base} * 1.285);
+
   @include rfs($font-size-base, --#{$prefix}body-font-size);
   --#{$prefix}body-font-weight: #{$font-weight-base};
   --#{$prefix}body-line-height: #{$line-height-base};
     --#{$prefix}link-color-rgb: #{to-rgb($link-color-dark)};
     --#{$prefix}link-hover-color-rgb: #{to-rgb($link-hover-color-dark)};
 
-    --#{$prefix}code-color: #{$code-color-dark};
+    // --#{$prefix}code-color: #{$code-color-dark}; // removed in v6
     --#{$prefix}highlight-color: #{$mark-color-dark};
     --#{$prefix}highlight-bg: #{$mark-bg-dark};
 
index a0ed0d40a27a0610f3b4faba91fd19a166b3bd30..ea2746449968cca7d765ae7a38f7111be2a7d310 100644 (file)
@@ -10,7 +10,7 @@
 // consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs.
 
 // scss-docs-start theme-color-variables
-$primary:       $purple-500 !default;
+$primary:       $blue-500 !default;
 $secondary:     $gray-600 !default;
 $success:       $green-500 !default;
 $info:          $cyan-500 !default;
@@ -167,6 +167,7 @@ $body-emphasis-color:       $black !default;
 
 $link-color:                              $primary !default;
 $link-decoration:                         underline !default;
+$link-underline-offset:                   .2em !default;
 $link-shade-percentage:                   20% !default;
 $link-hover-color:                        shift-color($link-color, $link-shade-percentage) !default;
 $link-hover-decoration:                   null !default;
@@ -204,7 +205,7 @@ $border-widths: (
   5: 5px
 ) !default;
 $border-style:                solid !default;
-$border-color:                $gray-300 !default;
+$border-color:                color.mix($gray-300, $gray-400) !default;
 $border-color-translucent:    rgba($black, .175) !default;
 // scss-docs-end border-variables
 
@@ -266,8 +267,8 @@ $font-family-code:            var(--#{$prefix}font-monospace) !default;
 
 // $font-size-root affects the value of `rem`, which is used for as well font sizes, paddings, and margins
 // $font-size-base affects the font size of the body text
-$font-size-root:              null !default;
-$font-size-base:              1rem !default; // Assumes the browser default, typically `16px`
+$font-size-root:              16px !default;
+$font-size-base:              14px !default; // Assumes the browser default, typically `16px`
 $font-size-sm:                $font-size-base * .875 !default;
 $font-size-lg:                $font-size-base * 1.25 !default;
 
@@ -546,8 +547,8 @@ $offcanvas-backdrop-opacity:        $modal-backdrop-opacity !default;
 
 // Code
 
-$code-font-size:                    $small-font-size !default;
-$code-color:                        $pink !default;
+$code-font-size:                    95% !default;
+$code-color:                        var(--#{$prefix}secondary-text) !default;
 
 $kbd-padding-y:                     .1875rem !default;
 $kbd-padding-x:                     .375rem !default;
@@ -578,7 +579,7 @@ $border-color-translucent-dark:     rgba($white, .15) !default;
 $headings-color-dark:               inherit !default;
 $link-color-dark:                   tint-color($primary, 40%) !default;
 $link-hover-color-dark:             shift-color($link-color-dark, -$link-shade-percentage) !default;
-$code-color-dark:                   tint-color($code-color, 40%) !default;
+// $code-color-dark:                   tint-color($code-color, 40%) !default;
 $mark-color-dark:                   $body-color-dark !default;
 $mark-bg-dark:                      $yellow-800 !default;
 
@@ -587,8 +588,8 @@ $mark-bg-dark:                      $yellow-800 !default;
 // Forms
 //
 
-$form-select-indicator-color-dark:  $body-color-dark !default;
-$form-select-indicator-dark:        url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#{$form-select-indicator-color-dark}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>") !default;
+// $form-select-indicator-color-dark:  $body-color-dark !default;
+// $form-select-indicator-dark:        url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#{$form-select-indicator-color-dark}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>") !default;
 
 $form-switch-color-dark:            rgba($white, .25) !default;
 $form-switch-bg-image-dark:         url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-color-dark}'/></svg>") !default;
index 47bd8321feb7ef493b4cbeeba990ecf9602207eb..313837b3d55d37ae928ae6a929d7278e506d979b 100644 (file)
@@ -33,7 +33,8 @@
 
   :root {
     @if $font-size-root != null {
-      @include font-size(var(--#{$prefix}root-font-size));
+      font-size: var(--#{$prefix}root-font-size);
+      // @include font-size(var(--#{$prefix}root-font-size));
     }
 
     @if $enable-smooth-scroll {
   // Links
 
   a {
-    color: rgba(var(--#{$prefix}link-color-rgb), var(--#{$prefix}link-opacity, 1));
-    text-decoration: $link-decoration;
+    color: var(--#{$prefix}link-color);
+    text-decoration: var(--#{$prefix}link-decoration);
+    text-underline-offset: $link-underline-offset;
 
     &:hover {
-      --#{$prefix}link-color-rgb: var(--#{$prefix}link-hover-color-rgb);
-      text-decoration: $link-hover-decoration;
+      color: var(--#{$prefix}link-hover-color);
+      text-decoration: var(--#{$prefix}link-hover-decoration);
     }
   }
 
index bdb223f1e2dd0ef095c36ab31210f9dca9335b6a..eed5ad3cd216b41673fad1b6edf7f920c8c101d9 100644 (file)
@@ -62,7 +62,237 @@ $form-switch-checked-bg-image:    url("data:image/svg+xml,<svg xmlns='http://www
 $form-switch-checked-bg-position: right center !default;
 // scss-docs-end form-switch-variables
 
+$check-border-color: var(--#{$prefix}border-color) !default;
+$check-checked-bg: var(--#{$prefix}primary-base) !default;
+$check-checked-border-color: $check-checked-bg !default;
+$check-indeterminate-bg: var(--#{$prefix}primary-base) !default;
+$check-indeterminate-border-color: $check-indeterminate-bg !default;
+$check-disabled-bg: var(--#{$prefix}secondary-bg) !default;
+$check-disabled-border-color: $check-disabled-bg !default;
+$check-disabled-opacity: .65 !default;
+
 @layer forms {
+  b-checkgroup,
+  b-radiogroup {
+    display: flex;
+    gap: var(--#{$prefix}gap, .5rem);
+    align-items: var(--#{$prefix}align-items, start);
+
+    .description {
+      color: var(--#{$prefix}secondary-text);
+    }
+  }
+
+  .check,
+  .radio {
+    --#{$prefix}check-bg: transparent;
+    --#{$prefix}check-border-color: #{$check-border-color};
+    --#{$prefix}check-checked-bg: #{$check-checked-bg};
+    --#{$prefix}check-checked-border-color: #{$check-checked-border-color};
+    --#{$prefix}check-indeterminate-bg: #{$check-indeterminate-bg};
+    --#{$prefix}check-indeterminate-border-color: #{$check-indeterminate-border-color};
+    --#{$prefix}check-disabled-bg: #{$check-disabled-bg};
+    --#{$prefix}check-disabled-border-color: #{$check-disabled-border-color};
+    --#{$prefix}check-disabled-opacity: #{$check-disabled-opacity};
+  }
+
+  .check {
+    display: grid;
+    grid-template-columns: repeat(1, minmax(0, 1fr));
+    margin-block: .125rem;
+
+    :where(svg, input) {
+      flex-shrink: 0;
+      grid-row-start: 1;
+      grid-column-start: 1;
+      width: 1rem;
+      height: 1rem;
+    }
+
+    :where(input) {
+      appearance: none;
+      // later: maybe set a tertiary bg color?
+      background-color: var(--#{$prefix}check-bg);
+      border: 1px solid var(--#{$prefix}check-border-color);
+      // stylelint-disable-next-line property-disallowed-list
+      border-radius: .25em;
+    }
+
+    :where(input:checked, input:indeterminate) {
+      background-color: var(--#{$prefix}check-checked-bg);
+      border-color: var(--#{$prefix}check-checked-border-color);
+    }
+
+    &:has(input:checked) .checked,
+    &:has(input:indeterminate) .indeterminate {
+      display: block;
+      color: var(--#{$prefix}primary-contrast);
+      stroke: currentcolor;
+    }
+
+    &:has(input:disabled) {
+      --#{$prefix}check-bg: var(--#{$prefix}check-disabled-bg);
+
+      ~ label {
+        color: var(--#{$prefix}secondary-text);
+        cursor: default;
+      }
+    }
+
+    &:has(input:disabled:checked) {
+      opacity: var(--#{$prefix}check-disabled-opacity);
+    }
+
+    :where(svg) {
+      pointer-events: none;
+    }
+
+    :where(svg path) {
+      display: none;
+    }
+  }
+
+  .radio {
+    position: relative;
+    flex-shrink: 0;
+    width: 1rem;
+    height: 1rem;
+    margin-block: .125rem;
+    appearance: none;
+    background-color: var(--#{$prefix}check-bg);
+    border: 1px solid var(--#{$prefix}check-border-color);
+    // stylelint-disable-next-line property-disallowed-list
+    border-radius: 50%;
+
+    &:checked {
+      color: var(--#{$prefix}primary-contrast);
+      background-color: var(--#{$prefix}check-checked-bg);
+      border-color: var(--#{$prefix}check-checked-border-color);
+
+      &::before {
+        position: absolute;
+        inset: .25rem;
+        content: "";
+        background-color: currentcolor;
+        // stylelint-disable-next-line property-disallowed-list
+        border-radius: 50%;
+      }
+    }
+
+    &:disabled {
+      --#{$prefix}check-bg: var(--#{$prefix}check-disabled-bg);
+
+      ~ label {
+        color: var(--#{$prefix}secondary-text);
+        cursor: default;
+      }
+    }
+  }
+
+  .switch {
+    --#{$prefix}switch-height: 1.25rem;
+    --#{$prefix}switch-width: calc(var(--#{$prefix}switch-height) * 1.5);
+    --#{$prefix}switch-padding: .0625rem;
+    --#{$prefix}switch-bg: var(--#{$prefix}secondary-bg);
+    --#{$prefix}switch-border-width: var(--#{$prefix}border-width);
+    --#{$prefix}switch-border-color: var(--#{$prefix}border-color);
+    --#{$prefix}switch-indicator-bg: var(--#{$prefix}white);
+    --#{$prefix}switch-checked-bg: var(--#{$prefix}primary-base);
+    --#{$prefix}switch-checked-indicator-bg: var(--#{$prefix}white);
+    --#{$prefix}switch-disabled-bg: var(--#{$prefix}secondary-bg);
+    --#{$prefix}switch-disabled-indicator-bg: var(--#{$prefix}secondary-text);
+
+    position: relative;
+    display: flex;
+    flex-shrink: 0;
+    align-items: stretch;
+    justify-content: flex-start;
+    width: var(--#{$prefix}switch-width);
+    height: var(--#{$prefix}switch-height);
+    padding: var(--#{$prefix}switch-padding);
+    background-color: var(--#{$prefix}switch-bg);
+    border: var(--#{$prefix}switch-border-width) solid var(--#{$prefix}switch-border-color);
+    // stylelint-disable-next-line property-disallowed-list
+    border-radius: 10rem;
+    box-shadow: inset 0 1px 2px rgba($black, .05);
+    // stylelint-disable-next-line property-disallowed-list
+    transition: .15s ease-in-out;
+    transition-property: padding-inline-start, background-color;
+
+    &::before {
+      flex-shrink: 0;
+      width: calc(var(--#{$prefix}switch-height) - calc(var(--#{$prefix}switch-padding) * 2) - var(--#{$prefix}switch-border-width) * 2);
+      height: calc(var(--#{$prefix}switch-height) - calc(var(--#{$prefix}switch-padding) * 2) - var(--#{$prefix}switch-border-width) * 2);
+      // width: calc(var(--#{$prefix}switch-height) - calc(var(--#{$prefix}switch-padding) * 2));
+      // height: calc(var(--#{$prefix}switch-height) - calc(var(--#{$prefix}switch-padding) * 2));
+      content: "";
+      background-color: var(--#{$prefix}switch-indicator-bg);
+      // stylelint-disable-next-line property-disallowed-list
+      border-radius: 50%;
+      box-shadow: 0 1px 2px rgba($black, .1);
+    }
+
+    input {
+      position: absolute;
+      inset: 0;
+      appearance: none;
+      background-color: transparent;
+    }
+
+    &:has(input:checked) {
+      padding-inline-start: calc(var(--#{$prefix}switch-height) / 2 + var(--#{$prefix}switch-padding));
+      background-color: var(--#{$prefix}primary-base);
+    }
+
+    &:has(input:disabled) {
+      --#{$prefix}switch-bg: var(--#{$prefix}switch-disabled-bg);
+      --#{$prefix}switch-indicator-bg: var(--#{$prefix}switch-disabled-indicator-bg);
+
+      &::before { opacity: .4; }
+
+      ~ label {
+        color: var(--#{$prefix}secondary-text);
+        cursor: default;
+      }
+    }
+  }
+  .switch-sm {
+    --#{$prefix}switch-height: 1em;
+  }
+  .switch-lg {
+    --#{$prefix}switch-height: 2em;
+  }
+
+  .select {
+    display: grid;
+    grid-template-columns: repeat(1, minmax(0, 1fr));
+
+    > select,
+    > svg {
+      grid-row-start: 1;
+      grid-column-start: 1;
+    }
+
+    > select {
+      padding: .5rem 3rem .5rem 1em;
+      font-size: 14px;
+      line-height: 20px;
+      appearance: none;
+      background-color: var(--#{$prefix}body-bg);
+      border: 1px solid var(--#{$prefix}border-color);
+      // stylelint-disable-next-line property-disallowed-list
+      border-radius: .5em;
+    }
+
+    > svg {
+      align-self: center;
+      justify-self: end;
+      width: 1rem;
+      height: 1rem;
+      margin-right: 1rem;
+    }
+  }
+
   .form-check {
     display: block;
     min-height: $form-check-min-height;
index 6f617ee0ea8534cf33d7fc3b8968351e1e882d61..c7ff34f416592b49782244c2c5693b73e7fbcd0e 100644 (file)
@@ -1,36 +1,45 @@
 @use "sass:math";
 @use "../config" as *;
 @use "../variables" as *;
+@use "../functions" as *;
 @use "../vendor/rfs" as *;
 @use "../mixins/border-radius" as *;
 @use "../mixins/box-shadow" as *;
+@use "../mixins/color-mode" as *;
 @use "../mixins/focus-ring" as *;
 @use "../mixins/gradients" as *;
 @use "../mixins/transition" as *;
 @use "form-variables" as *;
 
-//
-// General form controls (plus a few specific high-level interventions)
-//
-
 @layer forms {
   .form-control {
+    --#{$prefix}control-min-height: #{$control-min-height};
+    --#{$prefix}control-padding-y: #{$control-padding-y};
+    --#{$prefix}control-padding-x: #{$control-padding-x};
+    --#{$prefix}control-font-size: #{$control-font-size};
+    --#{$prefix}control-line-height: #{$control-line-height};
+    --#{$prefix}control-color: #{$control-color};
+    --#{$prefix}control-bg: #{$control-bg};
+    --#{$prefix}control-border-width: #{$control-border-width};
+    --#{$prefix}control-border-color: #{$control-border-color};
+    --#{$prefix}control-border-radius: #{$control-border-radius};
+    --#{$prefix}control-select-bg-color: #{$control-select-indicator-color};
+    --#{$prefix}control-select-bg: #{escape-svg($control-select-indicator)};
+    --#{$prefix}control-select-bg-position: #{$control-select-bg-position};
+    --#{$prefix}control-select-bg-size: #{$control-select-bg-size};
+
     display: block;
     width: 100%;
-    padding: $input-padding-y $input-padding-x;
-    font-family: $input-font-family;
-    @include font-size($input-font-size);
-    font-weight: $input-font-weight;
-    line-height: $input-line-height;
-    color: $input-color;
-    appearance: none; // Fix appearance for date inputs in Safari
-    background-color: $input-bg;
+    min-height: var(--#{$prefix}control-min-height);
+    padding: var(--#{$prefix}control-padding-y) var(--#{$prefix}control-padding-x);
+    font-size: var(--#{$prefix}control-font-size);
+    line-height: var(--#{$prefix}control-line-height);
+    color: var(--#{$prefix}control-color);
+    appearance: none;
+    background-color: var(--#{$prefix}control-bg);
     background-clip: padding-box;
-    border: $input-border-width solid $input-border-color;
-
-    // Note: This has no effect on <select>s in some browsers, due to the limited stylability of `<select>`s in CSS.
-    @include border-radius($input-border-radius, 0);
-
+    border: var(--#{$prefix}control-border-width) solid var(--#{$prefix}control-border-color);
+    @include border-radius(var(--#{$prefix}control-border-radius), 0);
     @include box-shadow($input-box-shadow);
     @include transition($input-transition);
 
 
     // File input buttons theming
     &::file-selector-button {
-      padding: $input-padding-y $input-padding-x;
-      margin: (-$input-padding-y) (-$input-padding-x);
-      margin-inline-end: $input-padding-x;
+      min-height: var(--#{$prefix}control-min-height);
+      padding: var(--#{$prefix}control-padding-y) var(--#{$prefix}control-padding-x);
+      margin: calc(var(--#{$prefix}control-padding-y) * -1) calc(var(--#{$prefix}control-padding-x) * -1);
+      margin-inline-end: var(--#{$prefix}control-padding-x);
       color: $form-file-button-color;
       @include gradient-bg($form-file-button-bg);
       pointer-events: none;
       border-color: inherit;
       border-style: solid;
       border-width: 0;
-      border-inline-end-width: $input-border-width;
+      border-inline-end-width: var(--#{$prefix}control-border-width);
       border-radius: 0; // stylelint-disable-line property-disallowed-list
       @include transition($btn-transition);
     }
     }
   }
 
+  // stylelint-disable selector-no-qualifying-type
+  select.form-control {
+    padding-right: calc(var(--#{$prefix}control-padding-x) * 3);
+    background-image: var(--#{$prefix}control-select-bg);
+    background-repeat: no-repeat;
+    background-position: var(--#{$prefix}control-select-bg-position);
+    background-size: var(--#{$prefix}control-select-bg-size);
+
+    &[multiple],
+    &[size]:not([size="1"]) {
+      padding-right: var(--#{$prefix}control-padding-x);
+      background-image: none;
+    }
+
+    @if $enable-dark-mode {
+      @include color-mode(dark) {
+        --#{$prefix}control-select-indicator: #{escape-svg($control-select-indicator-dark)};
+      }
+    }
+  }
+  // stylelint-enable selector-no-qualifying-type
+
   // Form control sizing
   //
   // Build on `.form-control` with modifier classes to decrease or increase the
   // Repeated in `_input_group.scss` to avoid Sass extend issues.
 
   .form-control-sm {
-    min-height: $input-height-sm;
-    padding: $input-padding-y-sm $input-padding-x-sm;
-    @include font-size($input-font-size-sm);
-    @include border-radius($input-border-radius-sm);
-
-    &::file-selector-button {
-      padding: $input-padding-y-sm $input-padding-x-sm;
-      margin: (-$input-padding-y-sm) (-$input-padding-x-sm);
-      margin-inline-end: $input-padding-x-sm;
-    }
+    --#{$prefix}control-min-height: #{$control-min-height-sm};
+    --#{$prefix}control-padding-y: #{$control-padding-y-sm};
+    --#{$prefix}control-padding-x: #{$control-padding-x-sm};
+    --#{$prefix}control-font-size: #{$control-font-size-sm};
+    --#{$prefix}control-line-height: #{$control-line-height-sm};
+    --#{$prefix}control-border-radius: #{$control-border-radius-sm};
   }
 
   .form-control-lg {
-    min-height: $input-height-lg;
-    padding: $input-padding-y-lg $input-padding-x-lg;
-    @include font-size($input-font-size-lg);
-    @include border-radius($input-border-radius-lg);
-
-    &::file-selector-button {
-      padding: $input-padding-y-lg $input-padding-x-lg;
-      margin: (-$input-padding-y-lg) (-$input-padding-x-lg);
-      margin-inline-end: $input-padding-x-lg;
-    }
+    --#{$prefix}control-min-height: #{$control-min-height-lg};
+    --#{$prefix}control-padding-y: #{$control-padding-y-lg};
+    --#{$prefix}control-padding-x: #{$control-padding-x-lg};
+    --#{$prefix}control-font-size: #{$control-font-size-lg};
+    --#{$prefix}control-line-height: #{$control-line-height-lg};
+    --#{$prefix}control-border-radius: #{$control-border-radius-lg};
   }
 
-  // Make sure textareas don't shrink too much when resized
-  // https://github.com/twbs/bootstrap/pull/29124
-  // stylelint-disable selector-no-qualifying-type
-  textarea {
-    &.form-control {
-      min-height: $input-height;
-    }
-
-    &.form-control-sm {
-      min-height: $input-height-sm;
-    }
-
-    &.form-control-lg {
-      min-height: $input-height-lg;
-    }
-  }
-  // stylelint-enable selector-no-qualifying-type
+  // // Make sure textareas don't shrink too much when resized
+  // // https://github.com/twbs/bootstrap/pull/29124
+  // // stylelint-disable selector-no-qualifying-type
+  // textarea {
+  //   &.form-control {
+  //     min-height: $input-height;
+  //   }
+
+  //   &.form-control-sm {
+  //     min-height: $input-height-sm;
+  //   }
+
+  //   &.form-control-lg {
+  //     min-height: $input-height-lg;
+  //   }
+  // }
+  // // stylelint-enable selector-no-qualifying-type
 
   .form-control-color {
     width: $form-color-width;
diff --git a/scss/forms/_form-select.scss b/scss/forms/_form-select.scss
deleted file mode 100644 (file)
index 5792edb..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-@use "../config" as *;
-@use "../colors" as *;
-@use "../variables" as *;
-@use "../functions" as *;
-@use "../vendor/rfs" as *;
-@use "../mixins/border-radius" as *;
-@use "../mixins/box-shadow" as *;
-@use "../mixins/color-mode" as *;
-@use "../mixins/focus-ring" as *;
-@use "../mixins/transition" as *;
-@use "form-variables" as *;
-
-// scss-docs-start form-select-variables
-$form-select-padding-y:             $input-padding-y !default;
-$form-select-padding-x:             $input-padding-x !default;
-$form-select-font-family:           $input-font-family !default;
-$form-select-font-size:             $input-font-size !default;
-$form-select-indicator-padding:     $form-select-padding-x * 3 !default; // Extra padding for background-image
-$form-select-font-weight:           $input-font-weight !default;
-$form-select-line-height:           $input-line-height !default;
-$form-select-color:                 $input-color !default;
-$form-select-bg:                    $input-bg !default;
-$form-select-disabled-color:        null !default;
-$form-select-disabled-bg:           $input-disabled-bg !default;
-$form-select-disabled-border-color: $input-disabled-border-color !default;
-$form-select-bg-position:           right $form-select-padding-x center !default;
-$form-select-bg-size:               16px 12px !default; // In pixels because image dimensions
-$form-select-indicator-color:       $gray-800 !default;
-$form-select-indicator:             url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#{$form-select-indicator-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>") !default;
-
-$form-select-feedback-icon-padding-end: $form-select-padding-x * 2.5 + $form-select-indicator-padding !default;
-$form-select-feedback-icon-position:    center right $form-select-indicator-padding !default;
-$form-select-feedback-icon-size:        $input-height-inner-half $input-height-inner-half !default;
-
-$form-select-border-width:        $input-border-width !default;
-$form-select-border-color:        $input-border-color !default;
-$form-select-border-radius:       $input-border-radius !default;
-$form-select-box-shadow:          var(--#{$prefix}box-shadow-inset) !default;
-
-$form-select-focus-border-color:  $input-focus-border-color !default;
-$form-select-focus-width:         $input-focus-width !default;
-// $form-select-focus-box-shadow:    0 0 0 $form-select-focus-width $input-btn-focus-color !default;
-
-$form-select-padding-y-sm:        $input-padding-y-sm !default;
-$form-select-padding-x-sm:        $input-padding-x-sm !default;
-$form-select-font-size-sm:        $input-font-size-sm !default;
-$form-select-border-radius-sm:    $input-border-radius-sm !default;
-
-$form-select-padding-y-lg:        $input-padding-y-lg !default;
-$form-select-padding-x-lg:        $input-padding-x-lg !default;
-$form-select-font-size-lg:        $input-font-size-lg !default;
-$form-select-border-radius-lg:    $input-border-radius-lg !default;
-
-$form-select-transition:          $input-transition !default;
-// scss-docs-end form-select-variables
-
-@layer forms {
-  .form-select {
-    --#{$prefix}form-select-bg-img: #{escape-svg($form-select-indicator)};
-
-    display: block;
-    width: 100%;
-    padding: $form-select-padding-y $form-select-indicator-padding $form-select-padding-y $form-select-padding-x;
-    font-family: $form-select-font-family;
-    @include font-size($form-select-font-size);
-    font-weight: $form-select-font-weight;
-    line-height: $form-select-line-height;
-    color: $form-select-color;
-    appearance: none;
-    background-color: $form-select-bg;
-    background-image: var(--#{$prefix}form-select-bg-img), var(--#{$prefix}form-select-bg-icon, none);
-    background-repeat: no-repeat;
-    background-position: $form-select-bg-position;
-    background-size: $form-select-bg-size;
-    border: $form-select-border-width solid $form-select-border-color;
-    @include border-radius($form-select-border-radius, 0);
-    @include box-shadow($form-select-box-shadow);
-    @include transition($form-select-transition);
-
-    &:focus-visible {
-      border-color: $form-select-focus-border-color;
-      @include focus-ring(true);
-    }
-
-    &[multiple],
-    &[size]:not([size="1"]) {
-      padding-right: $form-select-padding-x;
-      background-image: none;
-    }
-
-    &:disabled {
-      color: $form-select-disabled-color;
-      background-color: $form-select-disabled-bg;
-      border-color: $form-select-disabled-border-color;
-    }
-
-    // Remove outline from select box in FF
-    &:-moz-focusring {
-      color: transparent;
-      text-shadow: 0 0 0 $form-select-color;
-    }
-  }
-
-  .form-select-sm {
-    padding-top: $form-select-padding-y-sm;
-    padding-bottom: $form-select-padding-y-sm;
-    padding-left: $form-select-padding-x-sm;
-    @include font-size($form-select-font-size-sm);
-    @include border-radius($form-select-border-radius-sm);
-  }
-
-  .form-select-lg {
-    padding-top: $form-select-padding-y-lg;
-    padding-bottom: $form-select-padding-y-lg;
-    padding-left: $form-select-padding-x-lg;
-    @include font-size($form-select-font-size-lg);
-    @include border-radius($form-select-border-radius-lg);
-  }
-
-  @if $enable-dark-mode {
-    @include color-mode(dark) {
-      .form-select {
-        --#{$prefix}form-select-bg-img: #{escape-svg($form-select-indicator-dark)};
-      }
-    }
-  }
-}
index bde73334aed586dfae3fc767f8614cc616d99ae9..f7a002ddeb0ef17b46fe36afe86470b2562e892d 100644 (file)
@@ -1,18 +1,53 @@
 @use "../config" as *;
+@use "../colors" as *;
 @use "../variables" as *;
 
+$control-min-height: 2.5rem !default;
+$control-min-height-sm: 2rem !default;
+$control-min-height-lg: 3rem !default;
+$control-padding-y: .375rem !default;
+$control-padding-x: .75rem !default;
+$control-font-size: $font-size-base !default;
+$control-line-height: $line-height-base !default;
+$control-color: var(--#{$prefix}body-color) !default;
+$control-bg: var(--#{$prefix}body-bg) !default;
+$control-border-width: var(--#{$prefix}border-width) !default;
+$control-border-color: var(--#{$prefix}border-color) !default;
+$control-border-radius: var(--#{$prefix}border-radius) !default;
+
+$control-padding-y-sm: .25rem !default;
+$control-padding-x-sm: .5rem !default;
+$control-font-size-sm: $font-size-sm !default;
+$control-line-height-sm: $line-height-sm !default;
+$control-border-radius-sm: var(--#{$prefix}border-radius-sm) !default;
+
+$control-padding-y-lg: .5rem !default;
+$control-padding-x-lg: 1rem !default;
+$control-font-size-lg: $font-size-lg !default;
+$control-line-height-lg: $line-height-lg !default;
+$control-border-radius-lg: var(--#{$prefix}border-radius-lg) !default;
+
+$control-select-indicator-color:       $gray-600 !default;
+$control-select-indicator:             url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#{$control-select-indicator-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>") !default;
+$control-select-bg-position:           right $control-padding-x center !default;
+$control-select-bg-size:               16px 12px !default;
+
+$control-select-indicator-color-dark:  $body-color-dark !default;
+$control-select-indicator-dark:        url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#{$control-select-indicator-color-dark}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>") !default;
+
+
 // scss-docs-start input-btn-variables
 $input-btn-padding-y:         .375rem !default;
 $input-btn-padding-x:         .75rem !default;
-$input-btn-font-family:       null !default;
+// $input-btn-font-family:       null !default;
 $input-btn-font-size:         $font-size-base !default;
 $input-btn-line-height:       $line-height-base !default;
 
-$input-btn-focus-width:         $focus-ring-width !default;
-$input-btn-focus-color-opacity: $focus-ring-opacity !default;
-$input-btn-focus-color:         $focus-ring-color !default;
-$input-btn-focus-blur:          $focus-ring-blur !default;
-$input-btn-focus-box-shadow:    $focus-ring-box-shadow !default;
+// $input-btn-focus-width:         $focus-ring-width !default;
+// $input-btn-focus-color-opacity: $focus-ring-opacity !default;
+// $input-btn-focus-color:         $focus-ring-color !default;
+// $input-btn-focus-blur:          $focus-ring-blur !default;
+// $input-btn-focus-box-shadow:    $focus-ring-box-shadow !default;
 
 $input-btn-padding-y-sm:      .25rem !default;
 $input-btn-padding-x-sm:      .5rem !default;
@@ -22,15 +57,15 @@ $input-btn-padding-y-lg:      .5rem !default;
 $input-btn-padding-x-lg:      1rem !default;
 $input-btn-font-size-lg:      $font-size-lg !default;
 
-$input-btn-border-width:      var(--#{$prefix}border-width) !default;
+// $input-btn-border-width:      var(--#{$prefix}border-width) !default;
 // scss-docs-end input-btn-variables
 
 // scss-docs-start form-input-variables
 $input-padding-y:                       $input-btn-padding-y !default;
 $input-padding-x:                       $input-btn-padding-x !default;
-$input-font-family:                     $input-btn-font-family !default;
+// $input-font-family:                     $input-btn-font-family !default;
 $input-font-size:                       $input-btn-font-size !default;
-$input-font-weight:                     $font-weight-base !default;
+// $input-font-weight:                     $font-weight-base !default;
 $input-line-height:                     $input-btn-line-height !default;
 
 $input-padding-y-sm:                    $input-btn-padding-y-sm !default;
@@ -48,7 +83,7 @@ $input-disabled-border-color:           null !default;
 
 $input-color:                           var(--#{$prefix}body-color) !default;
 $input-border-color:                    var(--#{$prefix}border-color) !default;
-$input-border-width:                    $input-btn-border-width !default;
+$input-border-width:                    var(--#{$prefix}border-width) !default;
 $input-box-shadow:                      var(--#{$prefix}box-shadow-inset) !default;
 
 $input-border-radius:                   var(--#{$prefix}border-radius) !default;
@@ -70,9 +105,12 @@ $input-height-inner:                    add($input-line-height * 1em, $input-pad
 $input-height-inner-half:               add($input-line-height * .5em, $input-padding-y) !default;
 $input-height-inner-quarter:            add($input-line-height * .25em, $input-padding-y * .5) !default;
 
-$input-height:                          add($input-line-height * 1em, add($input-padding-y * 2, $input-height-border, false)) !default;
-$input-height-sm:                       add($input-line-height * 1em, add($input-padding-y-sm * 2, $input-height-border, false)) !default;
-$input-height-lg:                       add($input-line-height * 1em, add($input-padding-y-lg * 2, $input-height-border, false)) !default;
+// $input-height:                          add($input-line-height * 1em, add($input-padding-y * 2, $input-height-border, false)) !default;
+// $input-height-sm:                       add($input-line-height * 1em, add($input-padding-y-sm * 2, $input-height-border, false)) !default;
+// $input-height-lg:                       add($input-line-height * 1em, add($input-padding-y-lg * 2, $input-height-border, false)) !default;
+$input-height:                          2.5rem !default;
+$input-height-sm:                       2rem !default;
+$input-height-lg:                       3rem !default;
 
 $input-transition:                      border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
 
index 8d6de5be1842935ea1af57401e4ea0c070270b62..99db177f5f1d2a2d1ea894f7accf9de0f0b28c7a 100644 (file)
@@ -10,7 +10,7 @@
 // scss-docs-start input-group-variables
 $input-group-addon-padding-y:           $input-padding-y !default;
 $input-group-addon-padding-x:           $input-padding-x !default;
-$input-group-addon-font-weight:         $input-font-weight !default;
+// $input-group-addon-font-weight:         $input-font-weight !default;
 $input-group-addon-color:               $input-color !default;
 $input-group-addon-bg:                  var(--#{$prefix}tertiary-bg) !default;
 $input-group-addon-border-color:        $input-border-color !default;
@@ -64,7 +64,7 @@ $input-group-addon-border-color:        $input-border-color !default;
     align-items: center;
     padding: $input-group-addon-padding-y $input-group-addon-padding-x;
     @include font-size($input-font-size); // Match inputs
-    font-weight: $input-group-addon-font-weight;
+    // font-weight: $input-group-addon-font-weight;
     line-height: $input-line-height;
     color: $input-group-addon-color;
     text-align: center;
diff --git a/scss/forms/_switch.scss b/scss/forms/_switch.scss
new file mode 100644 (file)
index 0000000..67baf23
--- /dev/null
@@ -0,0 +1,11 @@
+@use "../config" as *;
+@use "../colors" as *;
+@use "../variables" as *;
+@use "../functions" as *;
+@use "../vendor/rfs" as *;
+@use "../mixins/border-radius" as *;
+@use "../mixins/box-shadow" as *;
+@use "../mixins/color-mode" as *;
+@use "../mixins/focus-ring" as *;
+@use "../mixins/transition" as *;
+@use "form-variables" as *;
index 57e6cd25ee11d45a32fd92638bb786651a5afb9d..a08cde8b396f223d51947494d0ae365492de89f7 100644 (file)
@@ -1,7 +1,6 @@
 @forward "labels";
 @forward "form-text";
 @forward "form-control";
-@forward "form-select";
 @forward "form-check";
 @forward "form-range";
 @forward "floating-labels";
index 36c4ccad709c6be0bf86ea6b0a8000afb06507d3..67c83c3eb63a9c80c847b2bffbb388ec1dbfbb8f 100644 (file)
@@ -1,13 +1,24 @@
+// stylelint-disable selector-no-qualifying-type
+
 @layer helpers {
   // scss-docs-start stacks
-  .hstack {
+  .hstack,
+  b-hstack {
     display: flex;
     flex-direction: row;
     align-items: center;
     align-self: stretch;
   }
+  .hstack-start,
+  b-hstack[align="start"] {
+    display: flex;
+    flex-direction: row;
+    align-items: flex-start;
+    align-self: stretch;
+  }
 
-  .vstack {
+  .vstack,
+  b-vstack {
     display: flex;
     flex: 1 1 auto;
     flex-direction: column;
index d8e732f5a416934d1883b8f713ee0801cc2c3747..a930467e19ce845d844ba8f821c1b41c8a155669 100644 (file)
   pages:
     - title: Overview
     - title: Form control
-    - title: Select
     - title: Checks & radios
+    - title: Checkbox
+    - title: Radio
+    - title: Switch
     - title: Range
     - title: Input group
     - title: Floating labels
index 4f864f7101287d3e399dd92adfcdf82644e43b72..56f4abffa955ed5a28872e16ac8dd3f066646797 100644 (file)
@@ -15,7 +15,7 @@ Bootstrap has a base `.btn` class that sets up basic styles such as padding and
 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.
 
 <Callout type="warning">
-If you are using the `.btn` class on its own, remember to at least define some explicit `:focus` and/or `:focus-visible` styles.
+When using `.btn` without a modifier, be sure to add some explicit `:focus-visible` styles.
 </Callout>
 
 ## Variants
index 7acb624ea6062d291e536140aae85308b19892e9..323e6546b4c52b6fb491c3991fee59d9edb9f991 100644 (file)
@@ -46,6 +46,6 @@ Several Bootstrap components include embedded SVGs in our CSS to style component
 - [Form switches]([[docsref:/forms/checks-radios#switches]])
 - [Form validation icons]([[docsref:/forms/validation#server-side]])
 - [Navbar toggle buttons]([[docsref:/components/navbar#responsive-behaviors]])
-- [Select menus]([[docsref:/forms/select]])
+{/* - [Select menus]([[docsref:/forms/select]]) */}
 
 Based on [community conversation](https://github.com/twbs/bootstrap/issues/25394), some options for addressing this in your own codebase include [replacing the URLs with locally hosted assets]([[docsref:/getting-started/webpack#extracting-svg-files]]), removing the images and using inline images (not possible in all components), and modifying your CSP. Our recommendation is to carefully review your own security policies and decide on the best path forward, if necessary.
diff --git a/site/src/content/docs/forms/checkbox.mdx b/site/src/content/docs/forms/checkbox.mdx
new file mode 100644 (file)
index 0000000..cd3aac1
--- /dev/null
@@ -0,0 +1,105 @@
+---
+title: Checkbox
+description: Highly customizable, native checkbox `<input>` elements for presenting toggleable options.
+toc: true
+---
+
+**Trialing new components with the following rules:**
+
+- New components are purely components, they don't assume layout of any kind.
+- New options for form layout:
+  - Use new form groups to lay out your forms: `.control-group`, `.check-group`, `.radio-group`.
+  - Use helpers to lay out your forms—`.hstack` and `.vstack`—with utilities.
+
+## Basic checkbox
+
+All checkbox styling is contained to a wrapper class, `.check`. This does the following:
+
+- Adds a stacking grid layout for the checkbox and custom SVG icon.
+- Overrides the default `<input>` appearance with themed colors.
+- Handles the toggling of separate paths in our custom SVG for the `:checked` and indeterminate states. Two `<path>`s are included in the SVG, one for each state, and the appropriate `<path>` is shown based on the `<input>`’s state.
+
+For folks looking to replace our provided icons, you'll need to add the `.checked` and `.indeterminate` classes to the `<path>`s and use them in a single `<svg>` element.
+
+Checkbox layout and labels are handled with additional HTML and CSS.
+
+<Example code={`<div class="check">
+    <input type="checkbox" id="check" checked />
+    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
+      <path class="checked" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
+      <path class="indeterminate" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/>
+    </svg>
+  </div>`} />
+
+## Indeterminate
+
+Checkboxes can utilize the indeterminate pseudo class when manually set via JavaScript. There is no available HTML attribute for specifying it.
+
+<Example addStackblitzJs class="bd-example-indeterminate" code={`<div class="check">
+    <input type="checkbox" id="checkIndeterminate" />
+    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
+      <path class="checked" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
+      <path class="indeterminate" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/>
+    </svg>
+  </div>`} />
+
+## Label
+
+Wrap the `.check` in a `<b-checkgroup>` layout component and add your label. We use a custom element for layout here that sets some basic flexbox styling.
+
+Consider margin utilities for additional spacing, and flex utilities for alignment.
+
+<Example code={`<b-checkgroup>
+    <div class="check">
+      <input type="checkbox" id="checkLabel" />
+      <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
+        <path class="checked" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
+        <path class="indeterminate" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/>
+      </svg>
+    </div>
+    <label for="checkLabel">Example new checkbox</label>
+  </b-checkgroup>`} />
+
+## Description
+
+With this layout approach, you can easily add a description or other content after the label. Here we use another custom element, `<b-vstack>`, to stack the label and description.
+
+<Example code={`<b-checkgroup>
+    <div class="check">
+      <input type="checkbox" id="checkDescription" />
+      <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
+        <path class="checked" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
+        <path class="indeterminate" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/>
+      </svg>
+    </div>
+    <b-vstack>
+      <label for="checkDescription">Example new checkbox</label>
+      <small class="description">Supporting description for the above label.</small>
+    </b-vstack>
+  </b-checkgroup>`} />
+
+## Disabled
+
+Add the `disabled` attribute and the associated `<label>`s are automatically styled to match with a lighter color to help indicate the input’s state.
+
+<Example code={`<b-checkgroup>
+    <div class="check">
+      <input type="checkbox" id="checkDisabled" disabled />
+      <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
+        <path class="checked" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
+        <path class="indeterminate" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/>
+      </svg>
+    </div>
+    <label for="checkDisabled">Example new checkbox</label>
+  </b-checkgroup>`} />
+
+<Example code={`<b-checkgroup>
+    <div class="check">
+      <input type="checkbox" id="checkDisabledChecked" checked disabled />
+      <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
+        <path class="checked" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
+        <path class="indeterminate" fill='none' stroke='currentcolor' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/>
+      </svg>
+    </div>
+    <label for="checkDisabledChecked">Example new checkbox</label>
+  </b-checkgroup>`} />
index 2c0d0b916b49217df3d001ee0ad25f5df6a1fafb..df3f1d3e6ef305245c994e923b55ea132d08762e 100644 (file)
@@ -1,9 +1,13 @@
 ---
 title: Form controls
-description: Give textual form controls like `<input>`s and `<textarea>`s an upgrade with custom styles, sizing, focus states, and more.
+description: Give textual `<input>`s, `<textarea>`s, and `<select>`s an upgrade with custom styles, sizing, focus states, and more.
 toc: true
 ---
 
+<Callout type="info">
+  **Heads up!** As of v6, `<select>` elements are now styled with the `.form-control` class. This reduces a ton of duplication and abstraction while making it easier to consistently customize the appearance of form controls.
+</Callout>
+
 ## Example
 
 Form controls are styled with a mix of Sass and CSS variables, allowing them to adapt to color modes and support any customization method.
@@ -13,17 +17,78 @@ Form controls are styled with a mix of Sass and CSS variables, allowing them to
     <input type="email" class="form-control" id="exampleFormControlInput1" placeholder="name@example.com">
   </div>
   <div class="mb-3">
+    <label for="exampleFormControlSelect1" class="form-label">Example select</label>
+    <select class="form-control" id="exampleFormControlSelect1">
+      <option>Open this select menu</option>
+      <option value="1">One</option>
+      <option value="2">Two</option>
+      <option value="3">Three</option>
+    </select>
+  </div>
+  <div>
     <label for="exampleFormControlTextarea1" class="form-label">Example textarea</label>
     <textarea class="form-control" id="exampleFormControlTextarea1" rows="3"></textarea>
   </div>`} />
 
 ## Sizing
 
-Set heights using classes like `.form-control-lg` and `.form-control-sm`.
+Change the size of a form control by using classes like `.form-control-lg` and `.form-control-sm`. This adjusts `height`, `padding`, `font-size`, `line-height`, and `border-radius`.
+
+<Example code={`<div class="mb-3">
+    <label for="largeInput" class="form-label">Large input</label>
+    <input class="form-control form-control-lg" type="text" id="largeInput" placeholder="Large input" aria-label="Large input example">
+  </div>
+  <div class="mb-3">
+    <label for="largeSelect" class="form-label">Large select</label>
+    <select class="form-control form-control-lg" id="largeSelect">
+      <option>Open this select menu</option>
+      <option value="1">One</option>
+      <option value="2">Two</option>
+      <option value="3">Three</option>
+    </select>
+  </div>
+  <div>
+    <label for="largeTextarea" class="form-label">Large textarea</label>
+    <textarea class="form-control form-control-lg" id="largeTextarea" rows="3" placeholder="Large textarea" aria-label="Large textarea example"></textarea>
+  </div>`} />
+
+<Example code={`<div class="mb-3">
+    <label for="smallInput" class="form-label">Small input</label>
+    <input class="form-control form-control-sm" type="text" id="smallInput" placeholder="Small input" aria-label="Small input example">
+  </div>
+  <div class="mb-3">
+    <label for="smallSelect" class="form-label">Small select</label>
+    <select class="form-control form-control-sm" id="smallSelect">
+      <option>Open this select menu</option>
+      <option value="1">One</option>
+      <option value="2">Two</option>
+      <option value="3">Three</option>
+    </select>
+  </div>
+  <div>
+    <label for="smallTextarea" class="form-label">Small textarea</label>
+    <textarea class="form-control form-control-sm" id="smallTextarea" rows="3" placeholder="Small textarea" aria-label="Small textarea example"></textarea>
+  </div>`} />
+
+## Select
+
+The `multiple` attribute is supported on select elements:
+
+<Example code={`<select class="form-select" multiple aria-label="Multiple select example">
+    <option selected>Open this select menu</option>
+    <option value="1">One</option>
+    <option value="2">Two</option>
+    <option value="3">Three</option>
+  </select>`} />
+
+As is the `size` attribute:
 
-<Example code={`<input class="form-control form-control-lg" type="text" placeholder=".form-control-lg" aria-label=".form-control-lg example">
-<input class="form-control" type="text" placeholder="Default input" aria-label="default input example">
-<input class="form-control form-control-sm" type="text" placeholder=".form-control-sm" aria-label=".form-control-sm example">`} />
+<Example code={`<select class="form-select" size="3" aria-label="Size 3 select example">
+    <option selected>Open this select menu</option>
+    <option value="1">One</option>
+    <option value="2">Two</option>
+    <option value="3">Three</option>
+  </select>`} />
 
 ## Form text
 
@@ -62,7 +127,13 @@ Inline text can use any typical inline HTML element (be it a `<span>`, `<small>`
 Add the `disabled` boolean attribute on an input to give it a grayed out appearance, remove pointer events, and prevent focusing.
 
 <Example code={`<input class="form-control" type="text" placeholder="Disabled input" aria-label="Disabled input example" disabled>
-<input class="form-control" type="text" value="Disabled readonly input" aria-label="Disabled input example" disabled readonly>`} />
+<input class="form-control" type="text" value="Disabled readonly input" aria-label="Disabled input example" disabled readonly>
+<select class="form-select" aria-label="Disabled select example" disabled>
+    <option selected>Open this select menu</option>
+    <option value="1">One</option>
+    <option value="2">Two</option>
+    <option value="3">Three</option>
+  </select>`} />
 
 ## Readonly
 
diff --git a/site/src/content/docs/forms/radio.mdx b/site/src/content/docs/forms/radio.mdx
new file mode 100644 (file)
index 0000000..bce0f3d
--- /dev/null
@@ -0,0 +1,44 @@
+---
+title: Radio
+description: Highly customizable, native radio `<input>` elements for choosing one option from many.
+toc: true
+---
+
+## Basic radio
+
+Similar to checkboxes, radios are styled with the `.radio` class. However, there's no custom SVG as we use a Unicode character for the checked state.
+
+<Example code={`<input type="radio" id="radio" class="radio" />`} />
+
+## Label
+
+Wrap the `.radio` in a `<b-radiogroup>` layout component and add your label. We use a custom element for layout here that sets some basic flexbox styling.
+
+<Example code={`<b-radiogroup>
+    <input type="radio" id="radioLabel" class="radio" />
+    <label for="radioLabel">Example new radio</label>
+  </b-radiogroup>
+  `} />
+
+## Description
+
+With this layout approach, you can easily add a description or other content after the label. Here we use another custom element, `<b-vstack>`, to stack the label and description.
+
+<Example code={`<b-radiogroup>
+    <input type="radio" id="radioLabelDescription" class="radio" />
+    <b-vstack>
+      <label for="radioLabelDescription">Example new radio</label>
+      <small class="description">Supporting description for the above label.</small>
+    </b-vstack>
+  </b-radiogroup>
+  `} />
+
+## Disabled
+
+Add the `disabled` attribute and the associated `<label>`s are automatically styled to match with a lighter color to help indicate the input’s state.
+
+<Example code={`<b-radiogroup>
+    <input type="radio" id="radioDisabled" class="radio" disabled />
+    <label for="radioDisabled">Example new radio</label>
+  </b-radiogroup>
+  `} />
diff --git a/site/src/content/docs/forms/select.mdx b/site/src/content/docs/forms/select.mdx
deleted file mode 100644 (file)
index 18ce856..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
----
-title: Select
-description: Customize the native `<select>`s with custom CSS that changes the element’s initial appearance.
-toc: true
----
-
-## Default
-
-Custom `<select>` menus need only a custom class, `.form-select` to trigger the custom styles. Custom styles are limited to the `<select>`’s initial appearance and cannot modify the `<option>`s due to browser limitations.
-
-<Example code={`<select class="form-select" aria-label="Default select example">
-    <option selected>Open this select menu</option>
-    <option value="1">One</option>
-    <option value="2">Two</option>
-    <option value="3">Three</option>
-  </select>`} />
-
-## Sizing
-
-You may also choose from small and large custom selects to match our similarly sized text inputs.
-
-<Example code={`<select class="form-select form-select-lg mb-3" aria-label="Large select example">
-    <option selected>Open this select menu</option>
-    <option value="1">One</option>
-    <option value="2">Two</option>
-    <option value="3">Three</option>
-  </select>
-
-  <select class="form-select form-select-sm" aria-label="Small select example">
-    <option selected>Open this select menu</option>
-    <option value="1">One</option>
-    <option value="2">Two</option>
-    <option value="3">Three</option>
-  </select>`} />
-
-The `multiple` attribute is also supported:
-
-<Example code={`<select class="form-select" multiple aria-label="Multiple select example">
-    <option selected>Open this select menu</option>
-    <option value="1">One</option>
-    <option value="2">Two</option>
-    <option value="3">Three</option>
-  </select>`} />
-
-As is the `size` attribute:
-
-<Example code={`<select class="form-select" size="3" aria-label="Size 3 select example">
-    <option selected>Open this select menu</option>
-    <option value="1">One</option>
-    <option value="2">Two</option>
-    <option value="3">Three</option>
-  </select>`} />
-
-## Disabled
-
-Add the `disabled` boolean attribute on a select to give it a grayed out appearance and remove pointer events.
-
-<Example code={`<select class="form-select" aria-label="Disabled select example" disabled>
-    <option selected>Open this select menu</option>
-    <option value="1">One</option>
-    <option value="2">Two</option>
-    <option value="3">Three</option>
-  </select>`} />
-
-## CSS
-
-### Sass variables
-
-<ScssDocs name="form-select-variables" file="scss/forms/_form-select.scss" />
diff --git a/site/src/content/docs/forms/switch.mdx b/site/src/content/docs/forms/switch.mdx
new file mode 100644 (file)
index 0000000..1b74cdb
--- /dev/null
@@ -0,0 +1,62 @@
+---
+title: Switch
+description: Custom toggle component built on top of native `<input>` checkbox elements.
+toc: true
+---
+
+## Basic switch
+
+Switches are built with checkboxes under the hood, so their HTML closely mirrors that of checkboxes, including optional layout components.
+
+Switches work by layering an invisible checkbox over the custom switch indicator. This preserves interactivity while giving you a completely customizable layer underneath.
+
+Switches also include the `switch` attribute for browsers that support it. This provides haptic feedback when toggling the switch.
+
+<Example code={`<div class="switch">
+    <input type="checkbox" value="" id="switch" switch>
+  </div>`} />
+
+## Label
+
+Wrap the `.switch` in a `<b-checkgroup>` layout component and add your label. We use a custom element for layout here that sets some basic flexbox styling. Switches are checkboxes under the hood, so we reused the same custom element here.
+
+Consider margin utilities for additional spacing, and flex utilities for alignment, especially when using small or large switches.
+
+<Example class="d-flex flex-column gap-3" code={`<b-checkgroup>
+    <div class="switch switch-sm mt-1">
+      <input type="checkbox" value="" id="switchSmLabel" switch>
+    </div>
+    <label for="switchSmLabel">Small switch</label>
+  </b-checkgroup>
+
+  <b-checkgroup>
+    <div class="switch">
+      <input type="checkbox" value="" id="switchMdLabel" switch>
+    </div>
+    <label for="switchMdLabel">Default switch</label>
+  </b-checkgroup>`} />
+
+## Disabled
+
+Add the `disabled` attribute and the associated `<label>`s are automatically styled to match with a lighter color to help indicate the input’s state.
+
+<Example class="d-flex flex-column gap-3" code={`<b-checkgroup>
+    <div class="switch">
+      <input type="checkbox" value="" id="switchDisabledLabel" switch disabled>
+    </div>
+    <label for="switchDisabledLabel">Disabled switch</label>
+  </b-checkgroup>`} />
+
+## Sizes
+
+Add a size modifier class to make your switch appear smaller or larger.
+
+<Example class="d-flex flex-column gap-3" code={`<div class="switch switch-sm">
+    <input type="checkbox" value="" id="switchSizeSm" switch>
+  </div>
+  <div class="switch">
+    <input type="checkbox" value="" id="switchSizeMd" switch>
+  </div>
+  <div class="switch switch-lg">
+    <input type="checkbox" value="" id="switchSizeLg" switch>
+  </div>`} />