Toggle group
Fuse several toggles into one segmented control, single- or multi-select.
Wrap two or more toggles in .toggle-group to fuse them
into one segmented control — the same border-collapse and inner-corner squaring as a
button group. Make it single-select by giving
the toggles radio inputs that share a name; make it multi-select with
checkbox inputs. Add role="group" and an aria-label so the set is announced.
Single select
Radios sharing a name — exactly one toggle stays on.
<div class="toggle-group" role="group" aria-label="Text alignment"> <label class="toggle" data-size="icon"> <input type="radio" name="align" aria-label="Align left" checked /> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" > <line x1="4" y1="6" x2="20" y2="6" /> <line x1="4" y1="12" x2="14" y2="12" /> <line x1="4" y1="18" x2="18" y2="18" /> </svg> </label> <label class="toggle" data-size="icon"> <input type="radio" name="align" aria-label="Align center" /> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" > <line x1="4" y1="6" x2="20" y2="6" /> <line x1="7" y1="12" x2="17" y2="12" /> <line x1="5" y1="18" x2="19" y2="18" /> </svg> </label> <label class="toggle" data-size="icon"> <input type="radio" name="align" aria-label="Align right" /> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" > <line x1="4" y1="6" x2="20" y2="6" /> <line x1="10" y1="12" x2="20" y2="12" /> <line x1="6" y1="18" x2="20" y2="18" /> </svg> </label></div>/** * _toggle-group.css — Toggle group (.toggle-group) * * The .button-group structure applied to .toggle children: a single-select set * (radios sharing a `name`) or a multi-select set (checkboxes) fused into one * segmented control. Inner corners are squared and the doubled border collapses to * one seam; the checked/focused/hovered toggle floats above its neighbours. * * @layer components * @requires layers.css, _variables.css, _toggle.css * @uses :has() — float the checked/focused toggle above its neighbours * @uses :not() — first/last edge selection for shared-border collapse * @uses logical border-radius longhands — RTL-safe corner rounding per orientation * @see https://ui.shadcn.com/docs/components/base/toggle-group */@layer zazz.components { /* =========================================================================== TOGGLE GROUP Joins adjacent .toggle children into one segmented unit. Children keep their own --toggle-radius, so the outer corners follow the toggles' size variant automatically — only the *inner* corners are squared and the doubled border is collapsed with a -1px overlap. =========================================================================== */ .toggle-group { display: flex; inline-size: fit-content; /* stretch so mixed-height children share one edge */ align-items: stretch; } /* =========================================================================== TOGGLE GROUP — HORIZONTAL (default) =========================================================================== */ .toggle-group:not([data-orientation="vertical"]) { flex-direction: row; } .toggle-group:not([data-orientation="vertical"]) > :not(:last-child) { border-start-end-radius: 0; border-end-end-radius: 0; } .toggle-group:not([data-orientation="vertical"]) > :not(:first-child) { /* pull onto the previous control's border so the seam is one line, not two */ margin-inline-start: -1px; border-start-start-radius: 0; border-end-start-radius: 0; } /* =========================================================================== TOGGLE GROUP — VERTICAL =========================================================================== */ .toggle-group[data-orientation="vertical"] { flex-direction: column; } .toggle-group[data-orientation="vertical"] > :not(:last-child) { border-end-start-radius: 0; border-end-end-radius: 0; } .toggle-group[data-orientation="vertical"] > :not(:first-child) { margin-block-start: -1px; border-start-start-radius: 0; border-start-end-radius: 0; }}Multi select
Checkboxes — any number can be on at once.
<div class="toggle-group" role="group" aria-label="Text formatting"> <label class="toggle"> <input type="checkbox" name="format" value="bold" checked /> <span>Bold</span> </label> <label class="toggle"> <input type="checkbox" name="format" value="italic" /> <span>Italic</span> </label> <label class="toggle"> <input type="checkbox" name="format" value="underline" checked /> <span>Underline</span> </label></div>/** * _toggle-group.css — Toggle group (.toggle-group) * * The .button-group structure applied to .toggle children: a single-select set * (radios sharing a `name`) or a multi-select set (checkboxes) fused into one * segmented control. Inner corners are squared and the doubled border collapses to * one seam; the checked/focused/hovered toggle floats above its neighbours. * * @layer components * @requires layers.css, _variables.css, _toggle.css * @uses :has() — float the checked/focused toggle above its neighbours * @uses :not() — first/last edge selection for shared-border collapse * @uses logical border-radius longhands — RTL-safe corner rounding per orientation * @see https://ui.shadcn.com/docs/components/base/toggle-group */@layer zazz.components { /* =========================================================================== TOGGLE GROUP Joins adjacent .toggle children into one segmented unit. Children keep their own --toggle-radius, so the outer corners follow the toggles' size variant automatically — only the *inner* corners are squared and the doubled border is collapsed with a -1px overlap. =========================================================================== */ .toggle-group { display: flex; inline-size: fit-content; /* stretch so mixed-height children share one edge */ align-items: stretch; } /* =========================================================================== TOGGLE GROUP — HORIZONTAL (default) =========================================================================== */ .toggle-group:not([data-orientation="vertical"]) { flex-direction: row; } .toggle-group:not([data-orientation="vertical"]) > :not(:last-child) { border-start-end-radius: 0; border-end-end-radius: 0; } .toggle-group:not([data-orientation="vertical"]) > :not(:first-child) { /* pull onto the previous control's border so the seam is one line, not two */ margin-inline-start: -1px; border-start-start-radius: 0; border-end-start-radius: 0; } /* =========================================================================== TOGGLE GROUP — VERTICAL =========================================================================== */ .toggle-group[data-orientation="vertical"] { flex-direction: column; } .toggle-group[data-orientation="vertical"] > :not(:last-child) { border-end-start-radius: 0; border-end-end-radius: 0; } .toggle-group[data-orientation="vertical"] > :not(:first-child) { margin-block-start: -1px; border-start-start-radius: 0; border-start-end-radius: 0; }}Vertical
Set data-orientation="vertical" on the wrapper to stack them.
<div class="toggle-group" data-orientation="vertical" role="group" aria-label="Map layer"> <label class="toggle"> <input type="radio" name="layer" value="map" checked /> <span>Map</span> </label> <label class="toggle"> <input type="radio" name="layer" value="satellite" /> <span>Satellite</span> </label> <label class="toggle"> <input type="radio" name="layer" value="terrain" /> <span>Terrain</span> </label></div>/** * _toggle-group.css — Toggle group (.toggle-group) * * The .button-group structure applied to .toggle children: a single-select set * (radios sharing a `name`) or a multi-select set (checkboxes) fused into one * segmented control. Inner corners are squared and the doubled border collapses to * one seam; the checked/focused/hovered toggle floats above its neighbours. * * @layer components * @requires layers.css, _variables.css, _toggle.css * @uses :has() — float the checked/focused toggle above its neighbours * @uses :not() — first/last edge selection for shared-border collapse * @uses logical border-radius longhands — RTL-safe corner rounding per orientation * @see https://ui.shadcn.com/docs/components/base/toggle-group */@layer zazz.components { /* =========================================================================== TOGGLE GROUP Joins adjacent .toggle children into one segmented unit. Children keep their own --toggle-radius, so the outer corners follow the toggles' size variant automatically — only the *inner* corners are squared and the doubled border is collapsed with a -1px overlap. =========================================================================== */ .toggle-group { display: flex; inline-size: fit-content; /* stretch so mixed-height children share one edge */ align-items: stretch; } /* =========================================================================== TOGGLE GROUP — HORIZONTAL (default) =========================================================================== */ .toggle-group:not([data-orientation="vertical"]) { flex-direction: row; } .toggle-group:not([data-orientation="vertical"]) > :not(:last-child) { border-start-end-radius: 0; border-end-end-radius: 0; } .toggle-group:not([data-orientation="vertical"]) > :not(:first-child) { /* pull onto the previous control's border so the seam is one line, not two */ margin-inline-start: -1px; border-start-start-radius: 0; border-end-start-radius: 0; } /* =========================================================================== TOGGLE GROUP — VERTICAL =========================================================================== */ .toggle-group[data-orientation="vertical"] { flex-direction: column; } .toggle-group[data-orientation="vertical"] > :not(:last-child) { border-end-start-radius: 0; border-end-end-radius: 0; } .toggle-group[data-orientation="vertical"] > :not(:first-child) { margin-block-start: -1px; border-start-start-radius: 0; border-start-end-radius: 0; }}API
| Attribute | Values |
|---|---|
data-orientation | vertical (omit = horizontal default) |
The group reads each child's own --toggle-radius, so the outer corners follow the
toggles' data-size automatically — only the inner corners are squared. You never
need to touch zazz/styles/.