Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/twbs/bootstrap.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartijn Cuppens <martijn.cuppens@gmail.com>2020-05-02 12:11:24 +0300
committerMark Otto <otto@github.com>2020-06-16 05:04:19 +0300
commit1a0a0858efa0e1e3c6bebd38058df8ad39ca27a5 (patch)
tree7135351b96e14bda512d175af447eb9201ea737c
parent1b2ea5efb1ca3cea21776bc9992a79aee47fba1a (diff)
Remove checkbox/radio toggle from button plugin in favor of a CSS only solution
-rw-r--r--js/src/button.js78
-rw-r--r--js/tests/unit/button.spec.js161
-rw-r--r--scss/_button-group.scss49
-rw-r--r--scss/_buttons.scss9
-rw-r--r--scss/forms/_form-check.scss6
-rw-r--r--scss/mixins/_buttons.scss12
-rw-r--r--site/content/docs/5.0/components/button-group.md24
-rw-r--r--site/content/docs/5.0/components/buttons.md45
-rw-r--r--site/content/docs/5.0/forms/checks.md51
-rw-r--r--site/content/docs/5.0/migration.md4
10 files changed, 122 insertions, 317 deletions
diff --git a/js/src/button.js b/js/src/button.js
index 67f3b96279..fd29e5218b 100644
--- a/js/src/button.js
+++ b/js/src/button.js
@@ -8,7 +8,6 @@
import { getjQuery } from './util/index'
import Data from './dom/data'
import EventHandler from './dom/event-handler'
-import SelectorEngine from './dom/selector-engine'
/**
* ------------------------------------------------------------------------
@@ -23,18 +22,10 @@ const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const CLASS_NAME_ACTIVE = 'active'
-const CLASS_NAME_DISABLED = 'disabled'
-const CLASS_NAME_FOCUS = 'focus'
-const SELECTOR_DATA_TOGGLE_CARROT = '[data-toggle^="button"]'
-const SELECTOR_DATA_TOGGLE = '[data-toggle="buttons"]'
-const SELECTOR_INPUT = 'input:not([type="hidden"])'
-const SELECTOR_ACTIVE = '.active'
-const SELECTOR_BUTTON = '.btn'
+const SELECTOR_DATA_TOGGLE = '[data-toggle="button"]'
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
-const EVENT_FOCUS_DATA_API = `focus${EVENT_KEY}${DATA_API_KEY}`
-const EVENT_BLUR_DATA_API = `blur${EVENT_KEY}${DATA_API_KEY}`
/**
* ------------------------------------------------------------------------
@@ -57,51 +48,8 @@ class Button {
// Public
toggle() {
- let triggerChangeEvent = true
- let addAriaPressed = true
-
- const rootElement = this._element.closest(SELECTOR_DATA_TOGGLE)
-
- if (rootElement) {
- const input = SelectorEngine.findOne(SELECTOR_INPUT, this._element)
-
- if (input && input.type === 'radio') {
- if (input.checked &&
- this._element.classList.contains(CLASS_NAME_ACTIVE)) {
- triggerChangeEvent = false
- } else {
- const activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE, rootElement)
-
- if (activeElement) {
- activeElement.classList.remove(CLASS_NAME_ACTIVE)
- }
- }
-
- if (triggerChangeEvent) {
- if (input.hasAttribute('disabled') ||
- rootElement.hasAttribute('disabled') ||
- input.classList.contains(CLASS_NAME_DISABLED) ||
- rootElement.classList.contains(CLASS_NAME_DISABLED)) {
- return
- }
-
- input.checked = !this._element.classList.contains(CLASS_NAME_ACTIVE)
- EventHandler.trigger(input, 'change')
- }
-
- input.focus()
- addAriaPressed = false
- }
- }
-
- if (addAriaPressed) {
- this._element.setAttribute('aria-pressed',
- !this._element.classList.contains(CLASS_NAME_ACTIVE))
- }
-
- if (triggerChangeEvent) {
- this._element.classList.toggle(CLASS_NAME_ACTIVE)
- }
+ // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method
+ this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))
}
dispose() {
@@ -136,10 +84,10 @@ class Button {
* ------------------------------------------------------------------------
*/
-EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE_CARROT, event => {
+EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {
event.preventDefault()
- const button = event.target.closest(SELECTOR_BUTTON)
+ const button = event.target.closest(SELECTOR_DATA_TOGGLE)
let data = Data.getData(button, DATA_KEY)
if (!data) {
@@ -149,22 +97,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE_CARROT, eve
data.toggle()
})
-EventHandler.on(document, EVENT_FOCUS_DATA_API, SELECTOR_DATA_TOGGLE_CARROT, event => {
- const button = event.target.closest(SELECTOR_BUTTON)
-
- if (button) {
- button.classList.add(CLASS_NAME_FOCUS)
- }
-})
-
-EventHandler.on(document, EVENT_BLUR_DATA_API, SELECTOR_DATA_TOGGLE_CARROT, event => {
- const button = event.target.closest(SELECTOR_BUTTON)
-
- if (button) {
- button.classList.remove(CLASS_NAME_FOCUS)
- }
-})
-
const $ = getjQuery()
/**
diff --git a/js/tests/unit/button.spec.js b/js/tests/unit/button.spec.js
index f7caf23f62..ac32b4b897 100644
--- a/js/tests/unit/button.spec.js
+++ b/js/tests/unit/button.spec.js
@@ -1,11 +1,9 @@
import Button from '../../src/button'
-import EventHandler from '../../src/dom/event-handler'
/** Test helpers */
import {
getFixture,
clearFixture,
- createEvent,
jQueryMock
} from '../helpers/fixture'
@@ -51,144 +49,6 @@ describe('Button', () => {
expect(btnTestParent.classList.contains('active')).toEqual(true)
})
-
- it('should trigger input change event when toggled button has input field', done => {
- fixtureEl.innerHTML = [
- '<div class="btn-group" data-toggle="buttons">',
- ' <label class="btn btn-primary">',
- ' <input type="radio" id="radio" autocomplete="off"> Radio',
- ' </label>',
- '</div>'
- ].join('')
-
- const input = fixtureEl.querySelector('input')
- const label = fixtureEl.querySelector('label')
-
- input.addEventListener('change', () => {
- expect().nothing()
- done()
- })
-
- label.click()
- })
-
- it('should not trigger input change event when input already checked and button is active', () => {
- fixtureEl.innerHTML = [
- '<button type="button" class="btn btn-primary active" data-toggle="buttons">',
- ' <input type="radio" id="radio" autocomplete="off" checked> Radio',
- '</button>'
- ].join('')
-
- const button = fixtureEl.querySelector('button')
-
- spyOn(EventHandler, 'trigger')
-
- button.click()
-
- expect(EventHandler.trigger).not.toHaveBeenCalled()
- })
-
- it('should remove active when an other radio button is clicked', () => {
- fixtureEl.innerHTML = [
- '<div class="btn-group btn-group-toggle" data-toggle="buttons">',
- ' <label class="btn btn-secondary active">',
- ' <input type="radio" name="options" id="option1" autocomplete="off" checked> Active',
- ' </label>',
- ' <label class="btn btn-secondary">',
- ' <input type="radio" name="options" id="option2" autocomplete="off"> Radio',
- ' </label>',
- ' <label class="btn btn-secondary">',
- ' <input type="radio" name="options" id="option3" autocomplete="off"> Radio',
- ' </label>',
- '</div>'
- ].join('')
-
- const option1 = fixtureEl.querySelector('#option1')
- const option2 = fixtureEl.querySelector('#option2')
-
- expect(option1.checked).toEqual(true)
- expect(option1.parentElement.classList.contains('active')).toEqual(true)
-
- const clickEvent = createEvent('click')
-
- option2.dispatchEvent(clickEvent)
-
- expect(option1.checked).toEqual(false)
- expect(option1.parentElement.classList.contains('active')).toEqual(false)
- expect(option2.checked).toEqual(true)
- expect(option2.parentElement.classList.contains('active')).toEqual(true)
- })
-
- it('should do nothing if the child is not an input', () => {
- fixtureEl.innerHTML = [
- '<div class="btn-group btn-group-toggle" data-toggle="buttons">',
- ' <label class="btn btn-secondary active">',
- ' <span id="option1">el 1</span>',
- ' </label>',
- ' <label class="btn btn-secondary">',
- ' <span id="option2">el 2</span>',
- ' </label>',
- ' <label class="btn btn-secondary">',
- ' <span>el 3</span>',
- ' </label>',
- '</div>'
- ].join('')
-
- const option2 = fixtureEl.querySelector('#option2')
- const clickEvent = createEvent('click')
-
- option2.dispatchEvent(clickEvent)
-
- expect().nothing()
- })
-
- it('should add focus class on focus event', () => {
- fixtureEl.innerHTML = '<button class="btn" data-toggle="button"><input type="text"></button>'
-
- const btn = fixtureEl.querySelector('.btn')
- const input = fixtureEl.querySelector('input')
-
- const focusEvent = createEvent('focus')
- input.dispatchEvent(focusEvent)
-
- expect(btn.classList.contains('focus')).toEqual(true)
- })
-
- it('should not add focus class', () => {
- fixtureEl.innerHTML = '<button data-toggle="button"><input type="text"></button>'
-
- const btn = fixtureEl.querySelector('button')
- const input = fixtureEl.querySelector('input')
-
- const focusEvent = createEvent('focus')
- input.dispatchEvent(focusEvent)
-
- expect(btn.classList.contains('focus')).toEqual(false)
- })
-
- it('should remove focus class on blur event', () => {
- fixtureEl.innerHTML = '<button class="btn focus" data-toggle="button"><input type="text"></button>'
-
- const btn = fixtureEl.querySelector('.btn')
- const input = fixtureEl.querySelector('input')
-
- const focusEvent = createEvent('blur')
- input.dispatchEvent(focusEvent)
-
- expect(btn.classList.contains('focus')).toEqual(false)
- })
-
- it('should not remove focus class on blur event', () => {
- fixtureEl.innerHTML = '<button class="focus" data-toggle="button"><input type="text"></button>'
-
- const btn = fixtureEl.querySelector('button')
- const input = fixtureEl.querySelector('input')
-
- const focusEvent = createEvent('blur')
- input.dispatchEvent(focusEvent)
-
- expect(btn.classList.contains('focus')).toEqual(true)
- })
})
describe('toggle', () => {
@@ -206,27 +66,6 @@ describe('Button', () => {
expect(btnEl.getAttribute('aria-pressed')).toEqual('true')
expect(btnEl.classList.contains('active')).toEqual(true)
})
-
- it('should handle disabled attribute on non-button elements', () => {
- fixtureEl.innerHTML = [
- '<div class="btn-group disabled" data-toggle="buttons" aria-disabled="true" disabled>',
- ' <label class="btn btn-danger disabled" aria-disabled="true" disabled>',
- ' <input type="checkbox" aria-disabled="true" autocomplete="off" disabled class="disabled">',
- ' </label>',
- '</div>'
- ].join('')
-
- const btnGroupEl = fixtureEl.querySelector('.btn-group')
- const btnDanger = fixtureEl.querySelector('.btn-danger')
- const input = fixtureEl.querySelector('input')
-
- const button = new Button(btnGroupEl)
-
- button.toggle()
-
- expect(btnDanger.hasAttribute('disabled')).toEqual(true)
- expect(input.checked).toEqual(false)
- })
})
describe('dispose', () => {
diff --git a/scss/_button-group.scss b/scss/_button-group.scss
index e3d2e4cc4c..a2cf7cf274 100644
--- a/scss/_button-group.scss
+++ b/scss/_button-group.scss
@@ -10,15 +10,17 @@
> .btn {
position: relative;
flex: 1 1 auto;
+ }
- // Bring the hover, focused, and "active" buttons to the front to overlay
- // the borders properly
- &:hover,
- &:focus,
- &:active,
- &.active {
- z-index: 1;
- }
+ // Bring the hover, focused, and "active" buttons to the front to overlay
+ // the borders properly
+ > .btn-toggle:checked + .btn,
+ > .btn-toggle:focus + .btn,
+ > .btn:hover,
+ > .btn:focus,
+ > .btn:active,
+ > .btn.active {
+ z-index: 1;
}
}
@@ -46,7 +48,11 @@
@include border-right-radius(0);
}
- > .btn:not(:first-child),
+ // - Target second buttons which are not part of toggle buttons
+ // - Target third or more child
+ // - Target buttons in a button group
+ > :not(.btn-toggle) + .btn,
+ > .btn:nth-child(n + 3),
> .btn-group:not(:first-child) > .btn {
@include border-left-radius(0);
}
@@ -132,28 +138,3 @@
@include border-top-radius(0);
}
}
-
-
-// Checkbox and radio options
-//
-// In order to support the browser's form validation feedback, powered by the
-// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use
-// `display: none;` or `visibility: hidden;` as that also hides the popover.
-// Simply visually hiding the inputs via `opacity` would leave them clickable in
-// certain cases which is prevented by using `clip` and `pointer-events`.
-// This way, we ensure a DOM element is visible to position the popover from.
-//
-// See https://github.com/twbs/bootstrap/pull/12794 and
-// https://github.com/twbs/bootstrap/pull/14559 for more information.
-
-.btn-group-toggle {
- > .btn,
- > .btn-group > .btn {
- input[type="radio"],
- input[type="checkbox"] {
- position: absolute;
- clip: rect(0, 0, 0, 0);
- pointer-events: none;
- }
- }
-}
diff --git a/scss/_buttons.scss b/scss/_buttons.scss
index 47f7fec141..67c70de3b1 100644
--- a/scss/_buttons.scss
+++ b/scss/_buttons.scss
@@ -24,12 +24,14 @@
text-decoration: if($link-hover-decoration == underline, none, null);
}
- &:focus,
- &.focus {
+ .btn-toggle:focus + &,
+ &:focus {
outline: 0;
box-shadow: $btn-focus-box-shadow;
}
+ .btn-toggle:checked + &,
+ .btn-toggle:active + &,
&:active,
&.active {
@include box-shadow($btn-active-box-shadow);
@@ -81,8 +83,7 @@
text-decoration: $link-hover-decoration;
}
- &:focus,
- &.focus {
+ &:focus {
text-decoration: $link-hover-decoration;
}
diff --git a/scss/forms/_form-check.scss b/scss/forms/_form-check.scss
index e7afd9b259..fef7be6fe0 100644
--- a/scss/forms/_form-check.scss
+++ b/scss/forms/_form-check.scss
@@ -134,3 +134,9 @@
display: inline-block;
margin-right: $form-check-inline-margin-right;
}
+
+.btn-toggle {
+ position: absolute;
+ clip: rect(0, 0, 0, 0);
+ pointer-events: none;
+}
diff --git a/scss/mixins/_buttons.scss b/scss/mixins/_buttons.scss
index 4bb9feb8ae..e8655105d5 100644
--- a/scss/mixins/_buttons.scss
+++ b/scss/mixins/_buttons.scss
@@ -25,8 +25,8 @@
border-color: $hover-border;
}
- &:focus,
- &.focus {
+ .btn-toggle:focus + &,
+ &:focus {
color: $hover-color;
@include gradient-bg($hover-background);
border-color: $hover-border;
@@ -38,6 +38,8 @@
}
}
+ .btn-toggle:checked + &,
+ .btn-toggle:active + &,
&:active,
&.active,
.show > &.dropdown-toggle {
@@ -83,11 +85,13 @@
border-color: $active-border;
}
- &:focus,
- &.focus {
+ .btn-toggle:focus + &,
+ &:focus {
box-shadow: 0 0 0 $btn-focus-width rgba($color, .5);
}
+ .btn-toggle:checked + &,
+ .btn-toggle:active + &,
&:active,
&.active,
&.dropdown-toggle.show {
diff --git a/site/content/docs/5.0/components/button-group.md b/site/content/docs/5.0/components/button-group.md
index 236cd146c9..15fef0e088 100644
--- a/site/content/docs/5.0/components/button-group.md
+++ b/site/content/docs/5.0/components/button-group.md
@@ -1,14 +1,14 @@
---
layout: docs
title: Button group
-description: Group a series of buttons together on a single line with the button group, and super-power them with JavaScript.
+description: Group a series of buttons together on a single line with the button group.
group: components
toc: true
---
## Basic example
-Wrap a series of buttons with `.btn` in `.btn-group`. Add on optional JavaScript radio and checkbox style behavior with [our buttons plugin]({{< docsref "/components/buttons#button-plugin" >}}).
+Wrap a series of buttons with `.btn` in `.btn-group`.
{{< example >}}
<div class="btn-group" role="group" aria-label="Basic example">
@@ -26,6 +26,26 @@ In order for assistive technologies (such as screen readers) to convey that a se
In addition, groups and toolbars should be given an explicit label, as most assistive technologies will otherwise not announce them, despite the presence of the correct role attribute. In the examples provided here, we use `aria-label`, but alternatives such as `aria-labelledby` can also be used.
{{< /callout >}}
+These classes can also be added to links. Use the `.active` class to highlight a link.
+
+{{< example >}}
+<div class="btn-group">
+ <a href="#" class="btn btn-secondary active">Active link</a>
+ <a href="#" class="btn btn-secondary">Link</a>
+ <a href="#" class="btn btn-secondary">Link</a>
+</div>
+{{< /example >}}
+
+## Outlined styles
+
+{{< example >}}
+<div class="btn-group" role="group" aria-label="Basic example">
+ <button type="button" class="btn btn-outline-secondary">Left</button>
+ <button type="button" class="btn btn-outline-secondary">Middle</button>
+ <button type="button" class="btn btn-outline-secondary">Right</button>
+</div>
+{{< /example >}}
+
## Button toolbar
Combine sets of button groups into button toolbars for more complex components. Use utility classes as needed to space out groups, buttons, and more.
diff --git a/site/content/docs/5.0/components/buttons.md b/site/content/docs/5.0/components/buttons.md
index b64dfdbec9..cc0887487e 100644
--- a/site/content/docs/5.0/components/buttons.md
+++ b/site/content/docs/5.0/components/buttons.md
@@ -75,15 +75,6 @@ Create block level buttons—those that span the full width of a parent—by add
<button type="button" class="btn btn-secondary btn-lg btn-block">Block level button</button>
{{< /example >}}
-## Active state
-
-Buttons will appear pressed (with a darker background, darker border, and inset shadow) when active. **There's no need to add a class to `<button>`s as they use a pseudo-class**. However, you can still force the same active appearance with `.active` (and include the <code>aria-pressed="true"</code> attribute) should you need to replicate the state programmatically.
-
-{{< example >}}
-<a href="#" class="btn btn-primary btn-lg active" role="button" aria-pressed="true">Primary link</a>
-<a href="#" class="btn btn-secondary btn-lg active" role="button" aria-pressed="true">Link</a>
-{{< /example >}}
-
## 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.
@@ -119,39 +110,15 @@ Do more with buttons. Control button states or create groups of buttons for more
Add `data-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 the `<button>`.
{{< example >}}
-<button type="button" class="btn btn-primary" data-toggle="button" aria-pressed="false" autocomplete="off">
- Single toggle
-</button>
-{{< /example >}}
-
-### Checkbox and radio buttons
-
-Bootstrap's `.button` styles can be applied to other elements, such as `<label>`s, to provide checkbox or radio style button toggling. Add `data-toggle="buttons"` to a `.btn-group` containing those modified buttons to enable their toggling behavior via JavaScript and add `.btn-group-toggle` to style the `<input>`s within your buttons. **Note that you can create single input-powered buttons or groups of them.**
-
-The checked state for these buttons is **only updated via `click` event** on the button. If you use another method to update the input—e.g., with `<input type="reset">` or by manually applying the input's `checked` property—you'll need to toggle `.active` on the `<label>` manually.
-
-Note that pre-checked buttons require you to manually add the `.active` class to the input's `<label>`.
-
-{{< example >}}
-<div class="btn-group-toggle" data-toggle="buttons">
- <label class="btn btn-secondary active">
- <input type="checkbox" checked autocomplete="off"> Checked
- </label>
-</div>
+<button type="button" class="btn btn-primary" data-toggle="button" autocomplete="off">Toggle button</button>
+<button type="button" class="btn btn-primary active" data-toggle="button" autocomplete="off" aria-pressed="true">Active toggle button</button>
+<button type="button" class="btn btn-primary" disabled data-toggle="button" autocomplete="off">Disabled toggle button</button>
{{< /example >}}
{{< example >}}
-<div class="btn-group btn-group-toggle" data-toggle="buttons">
- <label class="btn btn-secondary active">
- <input type="radio" name="options" id="option1" autocomplete="off" checked> Active
- </label>
- <label class="btn btn-secondary">
- <input type="radio" name="options" id="option2" autocomplete="off"> Radio
- </label>
- <label class="btn btn-secondary">
- <input type="radio" name="options" id="option3" autocomplete="off"> Radio
- </label>
-</div>
+<a href="#" class="btn btn-primary" role="button" data-toggle="button">Toggle link</a>
+<a href="#" class="btn btn-primary active" role="button" data-toggle="button" aria-pressed="true">Active toggle link</a>
+<a href="#" class="btn btn-primary disabled" role="button" data-toggle="button">Disabled toggle link</a>
{{< /example >}}
### Methods
diff --git a/site/content/docs/5.0/forms/checks.md b/site/content/docs/5.0/forms/checks.md
index e189d1bb9b..fec40b962b 100644
--- a/site/content/docs/5.0/forms/checks.md
+++ b/site/content/docs/5.0/forms/checks.md
@@ -209,3 +209,54 @@ Omit the wrapping `.form-check` for checkboxes and radios that have no label tex
<input class="form-check-input" type="radio" name="radioNoLabel" id="radioNoLabel1" value="" aria-label="...">
</div>
{{< /example >}}
+
+## Toggle buttons
+
+### Checkbox toggle buttons
+
+Bootstrap's `.btn` styles can be applied to `<label>`s, to provide checkbox style button toggling. Add an input with a `.btn-toggle` class as previous sibling to toggle the input state.
+
+{{< example >}}
+<input type="checkbox" class="btn-toggle" id="btn-toggle" autocomplete="off">
+<label class="btn btn-primary" for="btn-toggle">Single toggle</label>
+{{< /example >}}
+
+{{< example >}}
+<input type="checkbox" class="btn-toggle" id="btn-toggle-2" checked autocomplete="off">
+<label class="btn btn-primary" for="btn-toggle-2">Checked</label>
+{{< /example >}}
+
+### Radio toggle buttons
+
+Toggle buttons can be grouped in a [button group]({{< docsref "/components/button-group" >}}) if needed.
+
+{{< example >}}
+<div class="btn-group">
+ <input type="radio" class="btn-toggle" name="options" id="option1" autocomplete="off" checked>
+ <label class="btn btn-secondary" for="option1">Checked</label>
+
+ <input type="radio" class="btn-toggle" name="options" id="option2" autocomplete="off">
+ <label class="btn btn-secondary" for="option2">Radio</label>
+
+ <input type="radio" class="btn-toggle" name="options" id="option3" autocomplete="off">
+ <label class="btn btn-secondary" for="option3">Radio</label>
+</div>
+{{< /example >}}
+
+### Outlined styles
+
+{{< example >}}
+<input type="checkbox" class="btn-toggle" id="btn-toggle-outlined" autocomplete="off">
+<label class="btn btn-outline-primary" for="btn-toggle-outlined">Single toggle</label><br>
+
+<input type="checkbox" class="btn-toggle" id="btn-toggle-2-outlined" checked autocomplete="off">
+<label class="btn btn-outline-secondary" for="btn-toggle-2-outlined">Checked</label><br>
+
+<div class="btn-group">
+ <input type="radio" class="btn-toggle" 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-toggle" name="options-outlined" id="danger-outlined" autocomplete="off">
+ <label class="btn btn-outline-danger" for="danger-outlined">Danger radio</label>
+</div>
+{{< /example >}}
diff --git a/site/content/docs/5.0/migration.md b/site/content/docs/5.0/migration.md
index dbb649bb11..7edc898191 100644
--- a/site/content/docs/5.0/migration.md
+++ b/site/content/docs/5.0/migration.md
@@ -148,6 +148,10 @@ Badges were overhauled to better differentiate themselves from buttons and to be
- **Todo:** Removed `.badge-pill` for the `.rounded-pill` utility class
- **Todo:** Removed badge's hover and focus styles for `a.badge` and `button.badge`.
+### Buttons
+
+- The checkbox/radio toggle is removed from the button plugin in favour of a CSS only solution, which is documented in the [form checks]({{< docsref "/forms/checks#toggle-buttons" >}}) docs. The `.btn-toggle` class can be added to inputs, any label with `.btn` and modifier class can be used to theme the labels. [See #30650](https://github.com/twbs/bootstrap/pull/30650).
+
### Cards
- Removed the card columns in favor of a Masonry grid [See #28922](https://github.com/twbs/bootstrap/pull/28922).