Skip to content

Commit c4bedfd

Browse files
committed
Add new button-input-wrapper wrapper, expand CSS, expand docs
- `button-input-wrapper` Makes this arguably more contained/flexible, less prone to problems with varied markup authors may have - expanded the CSS to cover all extra styling required, including correct focus/hover/active styles and button group additions/tweaks - expanded docs considerably to give more examples or these checkbox/radio button buttons in action - would love to use outline variants, but it appears our outline buttons lack distinction between hovered and active/checked, making them useless for now. to be addressed in a separate PR, and then revisit this with nicer outline buttons that really show off things like button groups with checkboxes/radios nicely
1 parent bfe562f commit c4bedfd

File tree

5 files changed

+160
-48
lines changed

5 files changed

+160
-48
lines changed

scss/_button-group.scss

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,22 @@
3737
// Prevent double borders when buttons are next to each other
3838
> .btn:not(:first-child),
3939
> .btn-group:not(:first-child),
40-
> .btn:not(:first-of-type) {
40+
> .btn-input-wrapper:not(:first-child) {
4141
margin-left: -$btn-border-width;
4242
}
4343

4444
// Reset rounded corners
4545
> .btn:not(:last-child):not(.dropdown-toggle),
46-
> .btn:not(:last-of-type):not(.dropdown-toggle),
47-
> .btn-group:not(:last-child) > .btn {
46+
> .btn-group:not(:last-child) > .btn,
47+
> .btn-input-wrapper:not(:last-child) > .btn {
4848
@include border-right-radius(0);
4949
}
5050

5151
> .btn + .btn,
52-
> .btn:not(:first-of-type),
5352
> .btn-group + .btn,
54-
> .btn-group:not(:first-child) > .btn {
53+
> .btn-input-wrapper + .btn,
54+
> .btn-group:not(:first-child) > .btn,
55+
> .btn-input-wrapper:not(:first-child) > .btn {
5556
@include border-left-radius(0);
5657
}
5758
}
@@ -121,23 +122,25 @@
121122
}
122123

123124
> .btn:not(:first-child),
124-
> .btn:not(:first-of-type),
125-
> .btn-group:not(:first-child) {
125+
> .btn-group:not(:first-child),
126+
> .btn-input-wrapper:not(:first-child) {
126127
margin-top: -$btn-border-width;
127128
}
128129

129130
// Reset rounded corners
130131
> .btn:not(:last-child):not(.dropdown-toggle),
131-
> .btn:not(:last-of-type):not(.dropdown-toggle),
132-
> .btn-group:not(:last-child) > .btn {
132+
> .btn-group:not(:last-child) > .btn,
133+
> .btn-input-wrapper:not(:last-child) > .btn {
133134
@include border-bottom-radius(0);
134135
}
135136

136137
> .btn + .btn,
137-
> .btn ~ .btn:not(:first-of-type),
138138
> .btn-group + .btn,
139139
> .btn-group:not(:first-child) > .btn,
140-
> .btn-group:not(:first-of-type) > .btn {
140+
> .btn-group:not(:first-of-type) > .btn,
141+
> .btn-input-wrapper + .btn,
142+
> .btn-input-wrapper:not(:first-child) > .btn,
143+
> .btn-input-wrapper:not(:first-of-type) > .btn {
141144
@include border-top-radius(0);
142145
}
143146
}

scss/_buttons.scss

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,30 @@ input[type="button"] {
157157
margin-bottom: 0; // Override default `<label>` value
158158
}
159159
}
160+
161+
// Make the button input wrapper behave like a button
162+
.btn-input-wrapper {
163+
position: relative;
164+
display: inline-flex;
165+
vertical-align: middle; // match .btn alignment given font-size hack above
166+
167+
> .btn {
168+
position: relative;
169+
flex: 1 1 auto;
170+
}
171+
172+
> .btn-input:focus + .btn,
173+
> .btn-input:active + .btn {
174+
// Bring the buttons for focused abd actuve button inputs to the front to overlay
175+
// the borders properly
176+
177+
z-index: 1;
178+
}
179+
180+
&,
181+
& label {
182+
// remove the default bottom margin for <label> when used for a button wrapper
183+
margin-bottom: 0;
184+
}
185+
186+
}

scss/mixins/_buttons.scss

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
border-color: $hover-border;
1616
}
1717

18+
// :active for button checkbox/radio here as well, as focus is only given *after* it's clicked/activated, compared to buttons/links
1819
&:focus,
1920
&.focus,
20-
.btn-input:focus + & {
21+
.btn-input:focus + &,
22+
.btn-input:active + & {
2123
color: color-yiq($hover-background);
2224
@include gradient-bg($hover-background);
2325
border-color: $hover-border;
@@ -44,6 +46,7 @@
4446
&:not(:disabled):not(.disabled):active,
4547
&:not(:disabled):not(.disabled).active,
4648
.btn-input:checked + &,
49+
.btn-input:active + &,
4750
.show > &.dropdown-toggle {
4851
color: color-yiq($active-background);
4952
background-color: $active-background;
@@ -73,9 +76,11 @@
7376
border-color: $active-border;
7477
}
7578

79+
// :active for button checkbox/radio here as well, as focus is only given *after* it's clicked/activated, compared to buttons/links
7680
&:focus,
7781
&.focus,
78-
.btn-input:focus + & {
82+
.btn-input:focus + &,
83+
.btn-input:active + & {
7984
box-shadow: 0 0 0 $btn-focus-width rgba($color, .5);
8085
}
8186

@@ -88,6 +93,7 @@
8893
&:not(:disabled):not(.disabled):active,
8994
&:not(:disabled):not(.disabled).active,
9095
.btn-input:checked + &,
96+
.btn-input:active + &,
9197
.show > &.dropdown-toggle {
9298
color: color-yiq($active-background);
9399
background-color: $active-background;

site/content/docs/4.3/components/button-group.md

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,46 @@ Wrap a series of buttons with `.btn` in `.btn-group`.
2323
Combine button-like checkboxes and radio buttons into a seamless looking button group.
2424

2525
{{< example >}}
26+
<!-- Implicit <label> -->
2627
<div class="btn-group" role="group" aria-label="Checkboxes">
27-
<input type="checkbox" class="btn-input" id="btnGroupCheck1" checked>
28-
<label class="btn btn-secondary" for="btnGroupCheck1">
29-
Checkbox
28+
<label class="btn-input-wrapper">
29+
<input type="checkbox" class="btn-input">
30+
<span class="btn btn-secondary">
31+
Checkbox 1
32+
</span>
3033
</label>
31-
<input type="checkbox" class="btn-input" id="btnGroupCheck2">
32-
<label class="btn btn-secondary" for="btnGroupCheck2">
33-
Checkbox
34+
<label class="btn-input-wrapper">
35+
<input type="checkbox" class="btn-input">
36+
<span class="btn btn-secondary">
37+
Checkbox 2
38+
</span>
3439
</label>
35-
<input type="checkbox" class="btn-input" id="btnGroupCheck3">
36-
<label class="btn btn-secondary" for="btnGroupCheck3">
37-
Checkbox
40+
<label class="btn-input-wrapper">
41+
<input type="checkbox" class="btn-input">
42+
<span class="btn btn-secondary">
43+
Checkbox 3
44+
</span>
3845
</label>
3946
</div>
4047
{{< /example >}}
4148

4249
{{< example >}}
50+
<!-- Explicit <label> with for/id relationship -->
4351
<div class="btn-group" role="group" aria-label="Radio buttons">
44-
<input type="radio" class="btn-input" name="btnGroupRadio" id="btnGroupRadio1" checked>
45-
<label class="btn btn-secondary" for="btnGroupRadio1">Radio button</label>
52+
<div class="btn-input-wrapper">
53+
<input type="radio" class="btn-input" name="btnGroupRadio" id="btnGroupRadio1" checked>
54+
<label class="btn btn-secondary" for="btnGroupRadio1">Radio 1</label>
55+
</div>
4656

47-
<input type="radio" class="btn-input" name="btnGroupRadio" id="btnGroupRadio2">
48-
<label class="btn btn-secondary" for="btnGroupRadio2">Radio button</label>
57+
<div class="btn-input-wrapper">
58+
<input type="radio" class="btn-input" name="btnGroupRadio" id="btnGroupRadio2">
59+
<label class="btn btn-secondary" for="btnGroupRadio2">Radio 2</label>
60+
</div>
4961

50-
<input type="radio" class="btn-input" name="btnGroupRadio" id="btnGroupRadio3">
51-
<label class="btn btn-secondary" for="btnGroupRadio3">Radio button</label>
62+
<div class="btn-input-wrapper">
63+
<input type="radio" class="btn-input" name="btnGroupRadio" id="btnGroupRadio3">
64+
<label class="btn btn-secondary" for="btnGroupRadio3">Radio 3</label>
65+
</div>
5266
</div>
5367
{{< /example >}}
5468

@@ -230,14 +244,20 @@ Make a set of buttons appear vertically stacked rather than horizontally. **Spli
230244

231245
<div class="bd-example">
232246
<div class="btn-group-vertical" role="group" aria-label="Radio buttons">
233-
<input type="radio" class="btn-input" name="btnGroupVertRadio" id="btnGroupVertRadio1" checked>
234-
<label class="btn btn-secondary" for="btnGroupVertRadio1">Radio button</label>
235-
236-
<input type="radio" class="btn-input" name="btnGroupVertRadio" id="btnGroupVertRadio2">
237-
<label class="btn btn-secondary" for="btnGroupVertRadio2">Radio button</label>
238-
239-
<input type="radio" class="btn-input" name="btnGroupVertRadio" id="btnGroupVertRadio3">
240-
<label class="btn btn-secondary" for="btnGroupVertRadio3">Radio button</label>
247+
<label class="btn-input-wrapper">
248+
<input type="radio" class="btn-input" name="btnGroupVertRadio" checked>
249+
<span class="btn btn-secondary">Radio 1</span>
250+
</label>
251+
252+
<label class="btn-input-wrapper">
253+
<input type="radio" class="btn-input" name="btnGroupVertRadio">
254+
<span class="btn btn-secondary">Radio 2</span>
255+
</label>
256+
257+
<label class="btn-input-wrapper">
258+
<input type="radio" class="btn-input" name="btnGroupVertRadio">
259+
<span class="btn btn-secondary">Radio 3</span>
260+
</label>
241261
</div>
242262
</div>
243263

site/content/docs/4.3/components/buttons.md

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -106,28 +106,80 @@ Disabled buttons using the `<a>` element behave a bit different:
106106
The `.disabled` class uses `pointer-events: none` to try to disable the link functionality of `<a>`s, but that CSS property is not yet standardized. 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, add a `tabindex="-1"` attribute on these links (to prevent them from receiving keyboard focus) and use custom JavaScript to disable their functionality.
107107
{{< /callout >}}
108108

109-
## Checkbox and radio buttons
109+
## Checkboxes and radio buttons
110110

111-
Bootstrap lets you create checkboxes and radio buttons that look like regular buttons. However, as they rely on CSS next sibling selectors, they require a fairly specific markup structure to ensure that the styles are all applied correctly.
111+
Bootstrap lets you create checkboxes and radio buttons that look like regular buttons. However, as they rely on CSS next sibling selectors, they require a fairly specific markup structure with a `.btn-input-wrapper` to ensure that the styles are all applied correctly.
112112

113113
Note that pre-checked buttons require you to manually add the `checked` attribute to the `<input>`.
114114

115115
{{< example >}}
116-
<input type="checkbox" class="btn-input" id="btnInputCheckSingle" checked>
117-
<label class="btn btn-primary" for="btnInputCheckSingle">
118-
Checked button
116+
<!-- Explicit <label> with for/id relationship -->
117+
<div class="btn-input-wrapper">
118+
<input type="checkbox" class="btn-input" id="btnInputCheckSingle1" checked>
119+
<label class="btn btn-primary" for="btnInputCheckSingle1">
120+
Pre-checked checkbox
121+
</label>
122+
</div>
123+
124+
<div class="btn-input-wrapper">
125+
<input type="checkbox" class="btn-input" id="btnInputCheckSingle2">
126+
<label class="btn btn-primary" for="btnInputCheckSingle2">
127+
Another checkbox
128+
</label>
129+
</div>
130+
{{< /example >}}
131+
132+
{{< example >}}
133+
<!-- Implicit <label> -->
134+
<label class="btn-input-wrapper">
135+
<input type="checkbox" class="btn-input" checked>
136+
<span class="btn btn-primary">
137+
Pre-checked checkbox
138+
</span>
139+
</label>
140+
141+
<label class="btn-input-wrapper">
142+
<input type="checkbox" class="btn-input">
143+
<span class="btn btn-primary">
144+
Another checkbox
145+
</span>
119146
</label>
120147
{{< /example >}}
121148

122149
{{< example >}}
123-
<input type="radio" class="btn-input" name="btnInputRadio" id="btnInputRadio1" checked>
124-
<label class="btn btn-primary" for="btnInputRadio1">Radio button</label>
150+
<!-- Explicit <label> with for/id relationship -->
151+
<div class="btn-input-wrapper">
152+
<input type="radio" class="btn-input" name="btnInputRadio" id="btnInputRadio1" checked>
153+
<label class="btn btn-primary" for="btnInputRadio1">Radio 1</label>
154+
</div>
155+
156+
<div class="btn-input-wrapper">
157+
<input type="radio" class="btn-input" name="btnInputRadio" id="btnInputRadio2">
158+
<label class="btn btn-primary" for="btnInputRadio2">Radio 2</label>
159+
</div>
160+
161+
<div class="btn-input-wrapper">
162+
<input type="radio" class="btn-input" name="btnInputRadio" id="btnInputRadio3">
163+
<label class="btn btn-primary" for="btnInputRadio3">Radio 3</label>
164+
</div>
165+
{{< /example >}}
125166

126-
<input type="radio" class="btn-input" name="btnInputRadio" id="btnInputRadio2">
127-
<label class="btn btn-primary" for="btnInputRadio2">Radio button</label>
167+
{{< example >}}
168+
<!-- Implicit <label> -->
169+
<label class="btn-input-wrapper">
170+
<input type="radio" class="btn-input" name="btnInputRadioImp" checked>
171+
<span class="btn btn-primary">Radio 1</span>
172+
</label>
173+
174+
<label class="btn-input-wrapper">
175+
<input type="radio" class="btn-input" name="btnInputRadioImp">
176+
<span class="btn btn-primary">Radio 2</span>
177+
</label>
128178

129-
<input type="radio" class="btn-input" name="btnInputRadio" id="btnInputRadio3">
130-
<label class="btn btn-primary" for="btnInputRadio3">Radio button</label>
179+
<label class="btn-input-wrapper">
180+
<input type="radio" class="btn-input" name="btnInputRadioImp">
181+
<span class="btn btn-primary">Radio 3</span>
182+
</label>
131183
{{< /example >}}
132184

133185
## Button plugin
@@ -139,8 +191,12 @@ The button plugin allows you to create simple on/off toggle buttons.
139191
Add `data-toggle="button"` to toggle a button's `active` state and `aria-pressed` attribute. If you're pre-toggling a button, you must manually add the `.active` class **and** `aria-pressed="true"` to the `<button>`.
140192

141193
{{< example >}}
194+
<button type="button" class="btn btn-primary active" data-toggle="button" aria-pressed="true">
195+
Pre-toggled toggle
196+
</button>
197+
142198
<button type="button" class="btn btn-primary" data-toggle="button" aria-pressed="false">
143-
Single toggle
199+
Another toggle
144200
</button>
145201
{{< /example >}}
146202

0 commit comments

Comments
 (0)