Grupo de botones
Dispone unos pocos botones relacionados en una fila o en una columna, separados o unidos en una sola forma, coloca la acción que completa la tarea y la que la abandona donde las personas las esperan, y da nombre a los botones como grupo.
- Categoría
- Acciones
- Tokens
comp.button-group- Nivel
- Base
- Web
tanrei-button-groupschedule - SwiftUI
TanreiButtonGroupschedule - Jetpack Compose
TanreiButtonGroupschedule - Flutter
TanreiButtonGroupschedule - React Native
ButtonGroupschedule
Generado a partir de docs/status/status.json, specs/button-group.spec.json
Standard y connected
Los elementos son los botones que ya usas, con etiqueta o de solo icono, con sus propias propiedades y eventos. El grupo solo los coloca, los une cuando es connected y les da nombre cuando tiene una etiqueta.
Los ejemplos de Web y React muestran la especificación actual. SwiftUI, Compose, Flutter y React Native van rezagados: todavía ofrecen la revisión anterior del grupo de botones, una sola fila sin orientation, fullWidth, separation ni indicadores de orden, cuyos elementos connected siempre quedan a una separación estrecha y cuyos elementos de solo icono son un botón de icono aparte. Sus ejemplos muestran esa revisión hasta que se pongan al día.
<!-- dismissive va al inicio de una fila y confirming, al final; en una columna intercambian sus lugares. -->
<tanrei-button-group>
<tanrei-button variant="outlined" dismissive>Cancelar</tanrei-button>
<tanrei-button variant="tonal">Guardar borrador</tanrei-button>
<tanrei-button variant="filled" confirming>Publicar</tanrei-button>
</tanrei-button-group>
<!-- Una columna tan ancha como su contenedor: Permitir se dibuja encima de Ahora no. -->
<tanrei-button-group orientation="vertical" full-width>
<tanrei-button variant="tonal" dismissive>Ahora no</tanrei-button>
<tanrei-button variant="tonal" confirming>Permitir</tanrei-button>
</tanrei-button-group>
<!-- Una etiqueta da nombre al grupo. Cada conmutador conserva su propio selected. -->
<tanrei-button-group variant="connected" label="Estilo de texto">
<tanrei-button variant="tonal" toggle label="Negrita"><span slot="icon">format_bold</span></tanrei-button>
<tanrei-button variant="tonal" toggle label="Cursiva"><span slot="icon">format_italic</span></tanrei-button>
<tanrei-button variant="tonal" toggle label="Subrayado"><span slot="icon">format_underlined</span></tanrei-button>
</tanrei-button-group>
<script type="module">
import '@tanrei/web/components/button-group';
import '@tanrei/web/components/button';
// El grupo no emite eventos propios: escucha en los botones, como en cualquier otro lugar.
document.querySelector('[label="Negrita"]').addEventListener('change', (e) => setBold(e.detail.selected));
</script>import { Button, ButtonGroup } from '@tanrei/react';
// confirming y dismissive son atributos que el grupo lee de sus hijos.
<ButtonGroup>
<Button variant="outlined" dismissive onClick={cancel}>Cancelar</Button>
<Button variant="tonal" onClick={saveDraft}>Guardar borrador</Button>
<Button variant="filled" confirming onClick={publish}>Publicar</Button>
</ButtonGroup>
<ButtonGroup orientation="vertical" fullWidth>
<Button variant="tonal" dismissive onClick={notNow}>Ahora no</Button>
<Button variant="tonal" confirming onClick={allow}>Permitir</Button>
</ButtonGroup>
<ButtonGroup variant="connected" label="Estilo de texto">
<Button variant="tonal" toggle selected={bold} label="Negrita" onChange={(e) => setBold(e.detail.selected)}><span slot="icon">format_bold</span></Button>
<Button variant="tonal" toggle selected={italic} label="Cursiva" onChange={(e) => setItalic(e.detail.selected)}><span slot="icon">format_italic</span></Button>
</ButtonGroup>import Tanrei
TanreiButtonGroup {
TanreiButton("Cancelar", variant: .outlined) { cancel() }
TanreiButton("Guardar borrador", variant: .tonal) { saveDraft() }
TanreiButton("Publicar") { publish() }
}
TanreiButtonGroup(variant: .connected, label: "Estilo de texto") {
TanreiIconButton("Negrita", isSelected: $bold, variant: .tonal) { Image(systemName: "bold") }
TanreiIconButton("Cursiva", isSelected: $italic, variant: .tonal) { Image(systemName: "italic") }
TanreiIconButton("Subrayado", isSelected: $underline, variant: .tonal) { Image(systemName: "underline") }
}import io.github.chnotchy.tanrei.components.TanreiButton
import io.github.chnotchy.tanrei.components.TanreiButtonGroup
import io.github.chnotchy.tanrei.components.TanreiButtonGroupVariant
import io.github.chnotchy.tanrei.components.TanreiIconButton
import io.github.chnotchy.tanrei.primitives.TanreiText
import io.github.chnotchy.tanrei.tokens.TanreiButtonVariant
import io.github.chnotchy.tanrei.tokens.TanreiIconButtonVariant
TanreiButtonGroup {
TanreiButton(text = "Cancelar", onClick = ::cancel, variant = TanreiButtonVariant.Outlined)
TanreiButton(text = "Guardar borrador", onClick = ::saveDraft, variant = TanreiButtonVariant.Tonal)
TanreiButton(text = "Publicar", onClick = ::publish)
}
// contentDescription da nombre al grupo. Compose no tiene rol de grupo: es un grupo de recorrido.
TanreiButtonGroup(variant = TanreiButtonGroupVariant.Connected, contentDescription = "Estilo de texto") {
TanreiIconButton(
onClick = {},
contentDescription = "Negrita",
variant = TanreiIconButtonVariant.Tonal,
checked = bold,
onCheckedChange = { bold = it },
) { TanreiText("format_bold") }
TanreiIconButton(
onClick = {},
contentDescription = "Cursiva",
variant = TanreiIconButtonVariant.Tonal,
checked = italic,
onCheckedChange = { italic = it },
) { TanreiText("format_italic") }
}import 'package:tanrei/tanrei.dart';
TanreiButtonGroup(
children: <Widget>[
TanreiButton(variant: TanreiButtonVariant.outlined, onPressed: cancel, child: const Text('Cancelar')),
TanreiButton(variant: TanreiButtonVariant.tonal, onPressed: saveDraft, child: const Text('Guardar borrador')),
TanreiButton(onPressed: publish, child: const Text('Publicar')),
],
);
TanreiButtonGroup(
variant: TanreiButtonGroupVariant.connected,
semanticLabel: 'Estilo de texto',
children: <Widget>[
TanreiIconButton(
isSelected: bold,
onPressed: () => setState(() => bold = !bold),
variant: TanreiIconButtonVariant.tonal,
semanticLabel: 'Negrita',
icon: const Icon(AppSymbols.formatBold),
),
TanreiIconButton(
isSelected: italic,
onPressed: () => setState(() => italic = !italic),
variant: TanreiIconButtonVariant.tonal,
semanticLabel: 'Cursiva',
icon: const Icon(AppSymbols.formatItalic),
),
],
);import { Button, ButtonGroup, IconButton } from '@tanrei/react-native';
<ButtonGroup>
<Button variant="outlined" onPress={cancel}>Cancelar</Button>
<Button variant="tonal" onPress={saveDraft}>Guardar borrador</Button>
<Button onPress={publish}>Publicar</Button>
</ButtonGroup>
<ButtonGroup variant="connected" accessibilityLabel="Estilo de texto">
<IconButton variant="tonal" accessibilityLabel="Negrita" toggle selected={bold} onSelectedChange={setBold}>
format_bold
</IconButton>
<IconButton variant="tonal" accessibilityLabel="Cursiva" toggle selected={italic} onSelectedChange={setItalic}>
format_italic
</IconButton>
</ButtonGroup>orientation dispone los botones en una fila (horizontal, el valor predeterminado) o los apila en una columna (vertical), donde cada botón es tan ancho como la columna. fullWidth hace que el grupo llene su contenedor: los botones de una fila se reparten el ancho a partes iguales, y cada botón de una columna tiene ese ancho. Esa es la forma adecuada para las acciones al pie de una hoja o de un panel estrecho.
Dos indicadores en un botón le dicen al grupo cuál es su lugar, de modo que las dos orientaciones sean coherentes entre sí:
| Indicador | El botón | En una fila | En una columna |
|---|---|---|---|
confirming |
completa la tarea: Aceptar, Guardar, Siguiente, Eliminar | al final | arriba |
dismissive |
la abandona: Cancelar, Atrás, Ahora no | al inicio | abajo |
| ninguno | cualquier otra cosa | entre los dos, en el orden dado | entre los dos, en el orden dado |
Los mismos tres botones en una fila y en una columna
Ancho completo: una fila que se reparte el ancho y una columna
El foco del teclado y los lectores de pantalla siguen el orden de colocación, de modo que todas las personas encuentran las acciones en el orden en que se dibujan. Los indicadores describen la tarea, no el tono: en «Cancelar · Eliminar», «Eliminar» es la acción que confirma. Una acción destructiva se distingue por su propio aspecto (el destructive del botón) y su etiqueta, no por su lugar. Un diálogo y una hoja lateral colocan sus propias acciones con los mismos dos indicadores.
Un grupo connected une sus botones de una de dos maneras. Con un divisor, los botones se tocan, sus esquinas son rectas donde se encuentran y una línea marca cada unión. Con una separación, quedan a una distancia estrecha, con esquinas más cerradas donde se miran. El sistema de diseño elige una vez para todos los componentes que unen partes (el sistema predeterminado dibuja un divisor), un sistema de diseño puede tratar aparte el grupo de botones, y separation (auto, divider, gap) define un grupo concreto. Un grupo standard lo ignora.
Connected con un divisor y con una separación, en una fila y en una columna
Con un divisor, una unión lleva una línea solo donde los dos botones descansan sobre el mismo relleno. Entre un conmutador seleccionado y uno sin seleccionar, o entre un botón filled y uno outlined, el borde del relleno ya marca la unión, así que ahí no se dibuja ninguna línea.
Conmutadores unidos por un divisor: no hay línea junto al seleccionado
Connected: outlined, large y con un elemento deshabilitado
Anatomía
Propiedades
| Propiedad | Tipo | Valor predeterminado | Descripción |
|---|---|---|---|
variant | standard | connected | "standard" | standard spaces the Buttons apart. connected joins them into one shape with rounded outer ends, by a divider or by a narrow gap (separation), for a closely related set (Bold, Italic, Underline; Previous, Today, Next). |
orientation | horizontal | vertical | "horizontal" | horizontal lays the Buttons out in one row; vertical stacks them in one column, every Button as wide as the column. The confirming and dismissive Buttons change places with the orientation (BGR-13, BGR-14). |
separation | auto | divider | gap | "auto" | How the Buttons of a connected group are joined. divider: they touch, and a line is drawn on each seam. gap: they stand a narrow gap apart, with tighter corners where they face each other. Auto takes comp.button-group.connected.separation, the design system's answer (divider in the default system). Ignored while variant is standard. |
fullWidth | boolean | false | The group fills its container's width and its Buttons share it equally (in a column, each is the container's width). For the actions at the foot of a dialog, a picker or a side sheet. |
label | string | "" | The accessible name of the group ("Text style"). Optional: a group of actions that are each named by their own label needs none. |
Elementos
Los tipos de elemento que el componente repite. Un fixture enumera sus elementos en orden; cada plataforma nombra cada tipo según su propio estilo.
button
A Button in the group: the button component, placed, spaced and (when connected) given its corners and minimum size by the group, otherwise drawn and behaving as it does anywhere.
| Propiedad | Tipo | Valor predeterminado | Descripción |
|---|---|---|---|
variant | filled | tonal | elevated | outlined | text | "filled" | Its emphasis, as the Button's variant. |
size | small | medium | large | "medium" | Its height, as the Button's size. Give every Button of a group the same size. |
width | default | narrow | wide | "default" | An icon-only Button's width, as the Button's width. A connected group still widens it to its minimum (BGR-3). |
icon | string | "" | The glyph in the Button's icon slot, by its name in the system's icon set. Empty for a label-only Button. It is how a fixture fills that slot; in an application the icon slot is filled as the Button's own spec says. |
iconPosition | leading | trailing | "leading" | Which side of the label the icon is on, as the Button's iconPosition. |
label | string | "" | Its accessible name, as the Button's label prop. Required for an icon-only Button. |
toggle | boolean | false | As the Button's toggle. |
selected | boolean | false | As the Button's selected. |
disabled | boolean | false | As the Button's disabled. |
confirming | boolean | false | The Button completes the task (OK, Save, Next, Delete). The group places it at the end of a row and at the top of a column (BGR-13, BGR-14). It changes the Button's place only, never its look (BGR-19). |
dismissive | boolean | false | The Button backs out of the task (Cancel, Back, Not now). The group places it at the start of a row and at the bottom of a column (BGR-13, BGR-14). It changes the Button's place only, never its look (BGR-19). |
| Slot | Descripción |
|---|---|
label | Its label, as the Button's default slot. A fixture item's content is this label; an icon-only item has none. |
icon | Its glyph, as the Button's icon slot. A fixture fills it through the icon prop. |
| Evento | Descripción |
|---|---|
click | The Button was activated, as the Button's click. |
change | A toggle Button was switched, as the Button's change. |
| Especificación | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| Elemento | tanrei-button | TanreiButton | TanreiButton | TanreiButton | Button |
variant · Propiedad | variant | variant | variant | variant | variant |
size · Propiedad | size | size | size | size | size |
width · Propiedad | width | width | width | width | width |
icon · Propiedad | slot="icon" | icon | icon | icon | icon |
iconPosition · Propiedad | iconPosition | iconPosition | iconPosition | iconAlignment | iconPosition |
label · Propiedad | label | label | contentDescription | semanticLabel | accessibilityLabel |
toggle · Propiedad | toggle | isSelected | onCheckedChange | isSelected | toggle |
selected · Propiedad | selected | isSelected | checked | isSelected | selected |
disabled · Propiedad | disabled | disabled | enabled | onPressed | disabled |
confirming · Propiedad | confirming | tanreiGroupPlacement(.confirming) | Modifier.confirming() | TanreiButtonGroupItem.placement | confirming |
dismissive · Propiedad | dismissive | tanreiGroupPlacement(.dismissive) | Modifier.dismissive() | TanreiButtonGroupItem.placement | dismissive |
click · Evento | click | action | onClick | onPressed | onPress |
change · Evento | change | isSelected | onCheckedChange | onPressed | onSelectedChange |
| Notas | A tanrei-button child, as anywhere: its label is its content and its glyph a tanrei-icon in its icon slot. confirming and dismissive are boolean attributes on the child that the group reads (a MutationObserver re-places the items when they change, BGR-11); the Button itself draws nothing for them. | A TanreiButton child, as anywhere. A toggle is a Button given an isSelected binding. The two flags are one modifier, .tanreiGroupPlacement(.confirming) or (.dismissive), a LayoutValueKey the group's layout reads, as .layoutPriority is read by a stack; one enum cannot be both (BGR-18 cannot arise). | A TanreiButton child, as anywhere; a toggle is a Button given checked and onCheckedChange, and enabled is disabled's inverse. The two flags are modifiers of the group's scope, Modifier.confirming() and Modifier.dismissive(), parent data the group's layout reads, as RowScope.weight is. | A TanreiButton child, as anywhere; a null onPressed disables it, and a non-null isSelected makes it a toggle. The two flags are one ParentDataWidget, TanreiButtonGroupItem(placement: TanreiGroupPlacement.confirming | .dismissive, child:), as Flexible is for a Flex; one enum cannot be both (BGR-18 cannot arise). | A Button child, as anywhere. confirming and dismissive are props on the child that the group reads from its children; the Button itself draws nothing for them. |
Estados
Comportamiento
Requisitos en formato EARS. Cada implementación de plataforma se prueba contra estos identificadores. El texto de los requisitos se muestra tal como está en la especificación, en inglés.
| ID | Requisito |
|---|---|
BGR-1 | WHILE orientation is horizontal THE button group SHALL lay out its items in one row, in the order BGR-13 gives, each at its own size and width unless fullWidth is true (BGR-17): WHILE variant is standard comp.button-group.standard.gap apart and centred on the row's middle; WHILE variant is connected joined as its separation names (BGR-21) — comp.button-group.connected.gap apart under gap, touching under divider — and each stretched to the height of the tallest item (a Button's height is a minimum), so that the joined shape stays even when a label wraps or text is enlarged. |
BGR-2 | WHILE variant is connected and orientation is horizontal THE button group SHALL give the start corners of the first item and the end corners of the last the radius comp.button-group.connected.horizontal.outer-shape, and each corner that faces a neighbouring item the radius comp.button-group.connected.inner-shape WHILE its separation is gap and no radius, a square corner, WHILE its separation is divider (BGR-21), each capped at half the item's height and eased by sys.shape.corner-smoothing; the item's fill, outline, state layer, ripple and focus ring SHALL follow the resulting shape. WHILE variant is standard THE button group SHALL leave every corner of every item as the item draws it. |
BGR-3 | WHILE variant is connected THE button group SHALL make each item at least sys.target.touch for touch input and sys.target.pointer for fine pointers along the direction in which it meets its neighbours — wide in a row, high in a column — because joined items cannot extend a hit area over each other: an icon-only Button narrower than that widens its container around its centred icon, and a Button lower than that in a column grows its container around its centred content. Across the other direction the items SHALL extend their hit areas as BTN-5 says. A platform that cannot tell the input type SHALL use sys.target.touch. |
BGR-4 | WHILE variant is standard and the hit areas that two neighbouring items extend (BTN-5) overlap THE button group SHALL give every press on an item's visible bounds to that item, and a press in the gap between two items to the nearer one, so that neighbouring hit areas meet at the middle of the gap. comp.button-group.standard.gap is what keeps each item's target whole there: at sys.target.touch minus an item's width (in a row) or height (in a column) or more, no hit areas overlap and every item keeps its full target between its neighbours, as the default system's gap does for every Button size; a layer that narrows the gap narrows the targets between items. |
BGR-5 | THE button group SHALL NOT restyle its items beyond BGR-2, BGR-3, BGR-12, BGR-16, BGR-17, BGR-22 and BGR-23: each item SHALL render from its own tokens, answer hover, focus and press itself, switch itself WHILE its toggle is true, emit its own events, keep its own semantics and be its own Tab stop in the order BGR-15 gives; the group itself SHALL NOT take focus, SHALL NOT handle arrow keys and SHALL keep no selection of its own. |
BGR-6 | WHILE label is not empty THE button group SHALL expose itself to assistive technology as a group named by label that contains its items; WHILE label is empty THE button group SHALL expose no element of its own, only its items, unless the product names it (BGR-25). |
BGR-7 | WHERE orientation is horizontal, fullWidth is false and the row is wider than the space its layout gives it THE button group SHALL narrow its labelled Buttons in proportion to their widths, down to the width of their longest word, letting their labels wrap as a Button's label does when its layout gives it less width, and SHALL keep icon-only Buttons at their width; it SHALL NOT wrap the row onto a second line or scroll it, and in debug builds SHALL report a row that still does not fit. |
BGR-8 | WHEN the layout direction is right-to-left THE button group SHALL, in a row, put its start at the right — the dismissive items at the right and the confirming items at the left (BGR-13), the first placed item's outer corners on its right — and mirror which corners are inner corners; in a column it SHALL keep the top-to-bottom order of BGR-14. Each item's content SHALL follow the layout direction as the Button's does. |
BGR-9 | WHEN variant, orientation or separation names a value the button group does not define THE button group SHALL render with that prop's default (standard, horizontal, auto), and in debug builds report the unknown value. |
BGR-10 | WHILE the group holds fewer than two items, or content that is not a Tanrei Button, THE button group SHALL still render it, a single item keeping every corner as it draws it, and in debug builds report it; a platform that cannot inspect its content SHALL state the rule in its README and docs page. |
BGR-11 | WHEN an item becomes disabled, is added or is removed, or its confirming or dismissive changes, THE button group SHALL place its items again by BGR-13 and BGR-14 and recompute which corners are inner and outer corners, where its dividers are (BGR-24) and which items carry the outer padding of BGR-23, at once, without animation; a disabled item SHALL keep its place, its gap or its dividers, and its corners, and an item that has focus while it moves SHALL keep it. |
BGR-12 | WHILE orientation is vertical THE button group SHALL lay out its items in one column, top to bottom in the order BGR-14 gives, comp.button-group.standard.gap apart WHILE variant is standard and, WHILE variant is connected, comp.button-group.connected.gap apart under gap and touching under divider (BGR-21), and SHALL make every item as wide as the column — the widest item's width, or the container's width WHILE fullWidth is true — its content centred as a Button centres it in a wider container. WHERE the column is given less width than its widest item every item SHALL take that width and its label SHALL wrap as BGR-7 says; the column SHALL NOT scroll. |
BGR-13 | WHILE orientation is horizontal THE button group SHALL place its items from start to end in three runs: first the items whose dismissive is true, then the items with neither flag, then the items whose confirming is true; within each run the items SHALL keep the order they were given in. The start is the left in a left-to-right layout and the right in a right-to-left one (BGR-8). |
BGR-14 | WHILE orientation is vertical THE button group SHALL place its items from top to bottom in three runs: first the items whose confirming is true, then the items with neither flag, then the items whose dismissive is true; within each run the items SHALL keep the order they were given in, the first on top, in either layout direction. |
BGR-15 | THE button group SHALL make the focus order, and the order in which assistive technology reads its items, the placed order of BGR-13 and BGR-14 — start to end in a row, top to bottom in a column — whatever order the items were given in; a platform whose visual reordering the focus order does not follow (the web's CSS order) SHALL reorder the items themselves in the tree that focus and assistive technology walk. |
BGR-16 | WHILE variant is connected and orientation is vertical THE button group SHALL give the top corners of the first item and the bottom corners of the last the radius comp.button-group.connected.vertical.outer-shape, and every other corner of every item comp.button-group.connected.inner-shape WHILE its separation is gap and no radius, a square corner, WHILE its separation is divider (BGR-21), each capped at half the item's height and eased by sys.shape.corner-smoothing; the item's fill, outline, state layer, ripple and focus ring SHALL follow the resulting shape. |
BGR-17 | WHILE fullWidth is true and orientation is horizontal THE button group SHALL take the whole width its container gives it and divide it, less the gaps (none under divider), into equal shares, one per item, whatever each item holds: a label shorter than its share SHALL be centred in it, a label longer SHALL wrap as BGR-7 says, and an icon-only Button SHALL widen its container around its centred icon. WHERE a share is narrower than an item's minimum width (BGR-3, or an icon-only Button's own width) THE button group SHALL give that item its minimum, share the rest equally among the others, and in debug builds report it. WHILE fullWidth is false THE button group SHALL take its natural width. |
BGR-18 | IF an item's confirming and dismissive are both true THE button group SHALL place it among the items with neither flag and in debug builds report it. |
BGR-19 | THE button group SHALL NOT change an item's look, emphasis or semantics for its confirming or dismissive: the flags decide its place only. A destructive confirming action (Delete) takes its look from its own variant and its own destructive (BTN-40), and a Button is not made filled, or destructive, by being confirming. |
BGR-20 | THE button group SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the button group is shown THE button group SHALL draw itself for the new direction at once, without a motion of its own, keeping what it shows and its state. |
BGR-21 | WHILE variant is connected THE button group SHALL join its items as its separation names: WHILE separation is auto, as comp.button-group.connected.separation names, which follows sys.behavior.separation unless a design system sets it apart; WHILE separation is divider or gap, as that value names, whatever the tokens say. Under gap the items SHALL stand comp.button-group.connected.gap apart with nothing drawn between them and the inner corners of BGR-2 and BGR-16; under divider the items SHALL touch, with square inner corners and the divider of BGR-22 on each seam. WHILE variant is standard THE button group SHALL ignore separation and space its items by comp.button-group.standard.gap. WHEN separation, or the token it follows, changes THE button group SHALL draw the other form at once, without animation, keeping the order, the focus and the state of its items. A platform that cannot draw a choice SHALL fall back to the first value, divider, and state that in its README. |
BGR-22 | WHILE variant is connected and its separation is divider THE button group SHALL draw, on each seam between two neighbouring items that BGR-24 says carries one, a line comp.button-group.connected.divider.thickness thick, in the color BGR-24 names (comp.button-group.connected.divider.color, or comp.button-group.connected.divider.on-fill.color between two emphasis fills), centred on the seam, over the items' fills and state layers, running along the seam — top to bottom in a row, start to end in a column — and stopping comp.button-group.connected.divider.inset short of each end of it. The line SHALL take no space of its own, SHALL NOT take a press (a press on it goes to the item under the point), SHALL NOT be exposed to assistive technology and SHALL keep its color whatever state its neighbours are in. Each item SHALL draw neither its outline nor the ring of its elevation along an edge that meets a seam, whether or not a line is drawn on it, so that the divider is the only line a seam can carry and the outline of an outlined group runs once around the joined shape, in the color of its rules in the default system; an item's state layer, ripple and focus ring SHALL still follow that item's own shape. |
BGR-23 | WHILE variant is connected and orientation is horizontal THE button group SHALL add comp.button-group.connected.horizontal.outer-inset to the inline padding of the first item at its start and of the last item at its end, under either separation, so that content beside a rounded outer end looks as far from it as from the tighter corner on its other side: a labelled Button SHALL be wider by that amount with its content moved inward by it, and an icon-only Button SHALL be wider by that amount with its glyph centred in what is left, half of it nearer its neighbour. A single item (BGR-10) SHALL get none; a column SHALL add none, its ends not being full (BGR-16); WHILE fullWidth is true the shares of BGR-17 SHALL be divided first and the padding taken inside the end items' shares. In a right-to-left layout the start is the right, as BGR-8 says. |
BGR-24 | WHILE variant is connected and its separation is divider THE button group SHALL draw the line of BGR-22 on a seam only WHILE the two items it joins rest on the same fill: the colors from which they render their containers at rest — comp.button.outlined.container.color for an outlined Button, and so on for each variant, and for a toggle the selected or the unselected part of its variant (BTN-15) — are the same color in the active scheme, two containers that have no fill counting as the same. WHILE the two fills differ (a selected toggle beside an unselected one, a filled Button beside an outlined one) THE button group SHALL draw no line on that seam, because the edge of the fill already marks it, and the two items SHALL still draw neither outline nor elevation ring along it (BGR-22). A toggle of the text variant has no fill, selected or not, so its seams keep their lines. THE button group SHALL draw the line in comp.button-group.connected.divider.color, and in comp.button-group.connected.divider.on-fill.color WHILE both items rest on an emphasis fill: a filled Button that is not a toggle, or a selected toggle of the filled, tonal, elevated or outlined variant. It SHALL compare the fills the items have at rest: hover, focus and press SHALL neither add nor remove a line nor change its color, and a disabled item SHALL be compared by the look it has when enabled, so that disabling an item moves no line (BGR-11). WHEN an item's selected, toggle or variant changes THE button group SHALL add, remove or recolor the lines beside it at once, without animation. |
BGR-25 | WHERE the platform has ARIA attributes (the web), the host carries its group role (BGR-6) itself, so what the product writes on it is not moved, but for a name: WHILE label is not empty THE button group SHALL hold the product's aria-label and aria-labelledby on that host as their data-aria- twins, so that label is its whole name although the platform gives aria-labelledby the precedence, and WHEN label is emptied SHALL give them back as they were written; WHILE label is empty a name the product gives so — aria-label, or aria-labelledby naming an element — SHALL be its name. WHILE label is empty and the product names it so THE button group SHALL be exposed as a group named by that name, as with a label: a group the product names is a named group, and a name on an element with no role would name nothing. WHEN the product removes one, or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaLabelledByElements) to null, THE button group SHALL take it away. |
Correspondencia de nombres por plataforma
Paridad no significa API idénticas. Cada plataforma expresa el mismo contrato con su propio estilo.
| Especificación | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| Componente | tanrei-button-group | TanreiButtonGroup | TanreiButtonGroup | TanreiButtonGroup | ButtonGroup |
variant · Propiedad | variant | variant | variant | variant | variant |
orientation · Propiedad | orientation | orientation | orientation | orientation | orientation |
separation · Propiedad | separation | separation | separation | separation | separation |
fullWidth · Propiedad | fullWidth | fullWidth | fullWidth | fullWidth | fullWidth |
label · Propiedad | label | label | contentDescription | semanticLabel | accessibilityLabel |
| Notas | <tanrei-button-group variant="connected" orientation="vertical" full-width label="Text style"> holds tanrei-button children. It places them without moving them in the author's DOM: its shadow root has three slots in the order of the orientation (dismissive, unflagged, confirming in a row; confirming, unflagged, dismissive in a column) and assigns each child to one with manual slot assignment, so focus and the accessibility tree, which walk the flat tree, follow the placed order (BGR-15) and CSS order is not used. It sets role group and aria-label on its own element only while label is not empty (ElementInternals is not used for the role: axe-core does not see semantics set through it). It gives a connected item its corners and minimum size through CSS custom properties that Button reads on its container (--tanrei-group-start-radius, --tanrei-group-end-radius, and in a column the top and bottom radii, --tanrei-group-min-width, --tanrei-group-min-height); these are an internal contract between Tanrei elements, not a styling API. Each item also learns its place in the group through an internal controller, because three things cannot be done with custom properties alone: a label that may wrap (BGR-7, BGR-17), corners that never animate and pill ends held to half the measured height (BGR-11), and a hit area that stops at half the gap (BGR-4). It reports other children in debug builds (BGR-10). separation is an attribute (auto, divider, gap); auto reads the name in --tanrei-comp-button-group-connected-separation. Under divider the group draws each rule itself, in its shadow root over the seam, and tells each item through the same internal controller which of its edges meet a seam (no outline, no ring) and how much padding to add at an outer end (BGR-22, BGR-23). Owed on the web: orientation="vertical" must really stack the items (BGR-12, BGR-14, BGR-16) — today the element and the review gallery draw every vertical fixture as a row — and its fixtures must be drawn as columns in the gallery and the baselines. | Lagging: ships an earlier revision, a row of Buttons and the retired icon-only kind with no orientation, placement flags or full width. The target: TanreiButtonGroup(variant:, orientation:, fullWidth:, label:) { TanreiButton … }, a custom Layout that places its subviews by BGR-13 and BGR-14 and gives them .accessibilitySortPriority in that order so VoiceOver and keyboard focus follow it (BGR-15). The group passes a connected item its corners and minimum size through an environment value that TanreiButton reads (internal to the package). With a label it is .accessibilityElement(children: .contain) with .accessibilityLabel(label). The group reads its content through a variadic view, as the menu and tabs do, so it reports fewer than two items and content that is not a Tanrei Button in debug builds (BGR-10). Also owed: separation (auto, divider, gap), the divider of BGR-22, the outer padding of BGR-23 and the nine new fixtures; reading comp.button-group.connected.separation as a typed choice needs the native token emitters to write a component's choice of kind as a typed enumeration, which they do not yet. | Lagging: ships an earlier revision, a row of Buttons and the retired icon-only kind with no orientation, placement flags or full width. The target: TanreiButtonGroup(variant, orientation, fullWidth, contentDescription) { TanreiButton(…) }, a custom Layout whose items read a CompositionLocal for their corners and minimum size (internal to the library). It places the items by BGR-13 and BGR-14 and sets traversalIndex in that order so TalkBack and focus follow it (BGR-15). With a content description it is semantics { isTraversalGroup = true; contentDescription = … }; Compose has no group role. Debug builds report content that is not a Tanrei Button, and a group of fewer than two items (BGR-10). Also owed: separation (auto, divider, gap), the divider of BGR-22, the outer padding of BGR-23 and the nine new fixtures; reading comp.button-group.connected.separation as a typed choice needs the native token emitters to write a component's choice of kind as a typed enumeration, which they do not yet. | Lagging: ships an earlier revision, a row of Buttons and the retired icon-only kind with no orientation, placement flags or full width. The target: TanreiButtonGroup(variant:, orientation:, fullWidth:, semanticLabel:, children: [TanreiButton(…)]), a MultiChildRenderObjectWidget that places its children by BGR-13 and BGR-14 and wraps each in a FocusTraversalOrder and Semantics(sortKey:) in that order (BGR-15); the items read an InheritedWidget for their corners and minimum size (internal to the package). With a label it is Semantics(container: true, label:, explicitChildNodes: true). Children of another type are reported in debug builds (BGR-10). Also owed: separation (auto, divider, gap), the divider of BGR-22, the outer padding of BGR-23 and the nine new fixtures; reading comp.button-group.connected.separation as a typed choice needs the native token emitters to write a component's choice of kind as a typed enumeration, which they do not yet. | Lagging: ships an earlier revision, a row of Buttons and the retired icon-only kind with no orientation, placement flags or full width. The target: <ButtonGroup variant orientation fullWidth accessibilityLabel>{…}</ButtonGroup> with Button children, which it renders in the order of BGR-13 and BGR-14 (reordering the children themselves, so focus and the screen reader follow it, BGR-15) and which read a React context for their corners and minimum size (internal to the package). With a label it has role="group" and accessibilityLabel; without one it adds no accessibility element. Children of another type are reported in development builds (BGR-10). Also owed: separation (auto, divider, gap), the divider of BGR-22, the outer padding of BGR-23 and the nine new fixtures; reading comp.button-group.connected.separation as a typed choice needs the native token emitters to write a component's choice of kind as a typed enumeration, which they do not yet. |
Tokens
| Token | Alias de | Valor resuelto (claro) |
|---|---|---|
--tanrei-comp-button-group-connected-divider-color | sys.color.outline-variant | |
--tanrei-comp-button-group-connected-divider-inset | sys.space.space100 | 8px |
--tanrei-comp-button-group-connected-divider-on-fill-color | sys.color.outline | |
--tanrei-comp-button-group-connected-divider-thickness | sys.border.hairline | 1px |
--tanrei-comp-button-group-connected-gap | sys.space.space25 | 2px |
--tanrei-comp-button-group-connected-horizontal-outer-inset | sys.space.space50 | 4px |
--tanrei-comp-button-group-connected-horizontal-outer-shape | sys.shape.corner.full | 9999px |
--tanrei-comp-button-group-connected-inner-shape | sys.shape.corner.small | 6px |
--tanrei-comp-button-group-connected-separation | sys.behavior.separation | 0 |
--tanrei-comp-button-group-connected-vertical-outer-shape | sys.shape.corner.large | 12px |
--tanrei-comp-button-group-standard-gap | — | 16px |
Fixtures
Representaciones canónicas. Todas las plataformas capturan cada fixture con el mismo identificador para poder comparar los resultados.
standard
variant: standard items: 3 standard-icon-only
variant: standard label: Post actions items: 3 standard-mixed
variant: standard label: Message actions items: 3 connected
variant: connected label: Clipboard items: 3 connected-divider
variant: connected separation: divider label: Clipboard items: 3 connected-gap
variant: connected separation: gap label: Clipboard items: 3 connected-divider-filled
variant: connected separation: divider label: Clipboard items: 3 connected-gap-filled
variant: connected separation: gap label: Clipboard items: 3 connected-divider-tonal
variant: connected separation: divider label: Clipboard items: 3 connected-divider-elevated
variant: connected separation: divider label: Clipboard items: 3 connected-toggles
variant: connected label: Text style items: 3 connected-gap-toggles
variant: connected separation: gap label: Text style items: 3 connected-outlined-toggles
variant: connected separation: divider label: Text style items: 3 connected-mixed
variant: connected label: Date items: 4 connected-large
variant: connected label: Date items: 3 connected-disabled
variant: connected label: Clipboard items: 3 connected-vertical
variant: connected orientation: vertical label: Calendar view items: 3 connected-vertical-gap
variant: connected orientation: vertical separation: gap label: Calendar view items: 3 connected-vertical-toggles
variant: connected orientation: vertical label: Zoom items: 2 vertical-unflagged
variant: standard orientation: vertical items: 3 order-cancel-ok
variant: standard items: 2 order-cancel-ok-vertical
variant: standard orientation: vertical items: 2 order-back-next
variant: standard items: 2 order-back-next-vertical
variant: standard orientation: vertical items: 2 order-three-runs-vertical
variant: standard orientation: vertical items: 3 order-rtl
variant: standard direction: rtl items: 2 full-width
variant: standard fullWidth: true items: 2 full-width-vertical
variant: standard orientation: vertical fullWidth: true items: 2 Generado a partir de specs/button-group.spec.json, tokens/comp/button-group.tokens.json, design/tokens/
Usa un grupo de botones cuando unos pocos botones van juntos: el final de un formulario (Cancelar, Guardar borrador, Publicar), el formato de un editor (Negrita, Cursiva, Subrayado), los botones Anterior, Hoy y Siguiente de un calendario. El grupo fija su espaciado, los une cuando forman un conjunto y les da nombre cuando eso ayuda. Cada botón sigue siendo un botón.
Una fila de acciones para la pantalla o la selección actual, a la que se llega con una sola parada de tabulación y en la que las teclas de flecha se mueven entre ellas, es una barra de herramientas. Una acción principal unida a un menú con sus variaciones es un botón con menú: parece un grupo connected de dos, pero su segunda mitad siempre abre un menú.
Los cuatro ponen unas pocas cosas relacionadas en una fila, así que es fácil confundirlos. Se diferencian en lo que hace una pulsación y en cómo el teclado se mueve por ellos.
| Componente | Qué es | Selección | Teclado | Úsalo para |
|---|---|---|---|---|
| Botón segmentado (los preajustes de Material y cupertino; no está en el sistema Tanrei) | Un solo control: de dos a cinco opciones unidas que responden a una pregunta. | La suya propia, por valor: una opción o varias. | Una parada de tabulación. Las teclas de flecha se mueven entre las opciones, y con selección única también eligen. | Un ajuste con unas pocas respuestas fijas que se aplica de inmediato: Día, Semana, Mes; izquierda, centro, derecha. |
| Grupo de botones | Una fila o una columna de botones independientes, separados o unidos en una sola forma. | Ninguna. Un botón conmutador del grupo conserva su propio estado de activado o desactivado. | Cada botón es su propia parada de tabulación. | Acciones relacionadas: Cancelar, Guardar borrador, Publicar; Negrita, Cursiva y Subrayado como conmutadores independientes. |
| Chips | Controles pequeños e independientes en una fila que salta de línea o se desplaza. | Cada chip de filtro está activado o desactivado por sí mismo. | Cada chip es su propia parada de tabulación. | Filtros encima de una lista, respuestas sugeridas, elementos que introdujo la persona. |
| Pestañas | Vistas de un mismo tema, cada una con su propio panel. | Una pestaña: la vista que está en pantalla. | Una parada de tabulación. Las teclas de flecha mueven y seleccionan. | Cambiar el contenido que se muestra: Resumen, Actividad, Configuración. |
Una prueba rápida: si al pulsarlo hace algo, es un grupo de botones. Si cambia el contenido de debajo, son pestañas. Si define un valor, son botones de opción, o un botón segmentado donde el sistema lo incluya. Las opciones que son muchas o independientes son chips.
| Variante | Qué hace | Úsala para |
|---|---|---|
| Standard (predeterminada) | Separa los botones lo suficiente para que cada uno conserve entero su objetivo táctil entre sus vecinos. Cada botón conserva sus propias esquinas. | Acciones que están juntas pero se sostienen por sí solas, como Cancelar y Guardar en un formulario. |
| Connected | Une los botones en una sola forma con extremos exteriores redondeados, mediante un divisor o una separación estrecha (consulta Unidos por un divisor o por una separación). Cada botón tiene como mínimo el ancho de un objetivo y se estira hasta la altura del más alto. | Un conjunto estrechamente relacionado que se lee como un solo control: Negrita, Cursiva, Subrayado; Anterior, Hoy, Siguiente. |
En un grupo connected solo cambian las esquinas que dan a un vecino: rectas con un divisor, más cerradas con una separación. El relleno, el contorno, la capa de estado, la onda y el anillo de foco siguen la forma unida, y los extremos exteriores redondeados de una fila unida reciben un poco más de margen interior, de modo que el contenido de los botones de los extremos se ve equilibrado. Un botón pequeño de solo icono se ensancha alrededor de su icono centrado hasta el ancho del objetivo, porque los botones unidos no pueden extenderse unos sobre otros.
Usa una fila de forma predeterminada. Usa una columna donde los botones no cabrían uno al lado del otro con sus etiquetas completas, como las acciones de una hoja estrecha o de un panel del ancho de un teléfono, o junto a un contenido que discurre en vertical, como los controles de zoom de un mapa. Marca los botones que completan la tarea y los que la abandonan, para que el orden siga siendo el que las personas esperan cuando un diseño de página pasa de fila a columna.
- Dales el mismo tamaño a todos los botones de un grupo.
- Gasta el énfasis como en cualquier otro lugar: un solo botón filled como máximo, normalmente al final.
- Dale una etiqueta al grupo cuando sus botones solo tengan sentido juntos, como «Estilo de texto». Omítela en una fila de acciones en la que cada una dice lo que hace.
- Usa una sola variante para los botones de un grupo connected, para que la forma unida se lea como una sola.
- Marca como
confirmingel botón que completa la tarea y comodismissiveel que la abandona, y deja que el grupo los coloque. Escríbelos en cualquier orden; el grupo no los mueve en tu marcado. - Deja
separationenautosalvo que un grupo deba ser distinto, para que todos los controles unidos del producto se distingan de la misma manera.
- No uses botones conmutadores en un grupo para opciones que se excluyen entre sí, como la alineación a la izquierda, al centro y a la derecha. El grupo no hace que sus conmutadores sean excluyentes. Usa pestañas donde la elección cambia el contenido de debajo, botones de opción donde define un valor, o un botón segmentado donde el sistema lo incluya.
- No pongas en un grupo nada que no sean Buttons de Tanrei, con etiqueta o de solo icono: ni campos, ni chips, ni menús. Las compilaciones de depuración avisan de cualquier otro contenido.
- No uses los indicadores para el tono. Un «Eliminar» destructivo que completa la tarea es
confirming, y toma su aspecto de su propiodestructive; un botón con ambos indicadores se coloca como si no tuviera ninguno, y se avisa de ello. - No esperes que la fila salte de línea. Una fila que no cabe estrecha sus Buttons y ajusta sus etiquetas en varias líneas. Nunca pasa a una segunda línea ni se desplaza.
- No mezcles tamaños en un mismo grupo. El grupo no cambia el tamaño de sus botones.
- Rol
group- Etiquetado
- label, when given, names the group ("Text style"). Each item is named as it is anywhere: a Button by its label text, an icon-only Button by its label prop.
- Objetivo mínimo
- El área de pulsación cumple el tamaño mínimo de objetivo aunque el tamaño visual sea menor. minimum 32px · pointer 44px · spacing 8px · touch 48px
Teclado
| Teclas | Acción |
|---|---|
| Tab / Shift+Tab | Moves focus to each enabled item in turn in the placed order (BGR-15): start to end in a row, top to bottom in a column. |
| Enter / Space | Activates the focused item, as the Button itself says (a toggle switches). |
Notas
- A button group is a layout of actions: each item is its own Tab stop with its own semantics. A row that should be one Tab stop with arrow keys is a toolbar; one choice among several is a single-selection control: tabs where it changes the content below, a radio group where it sets a value, or a segmented button in a system that ships one.
- A toggle Button in a group keeps its own on/off state; the group does not make its toggles exclusive. One choice among several is a single-selection control: tabs where it changes the content below, a radio group where it sets a value, or a segmented button in a system that ships one.
- confirming and dismissive are not announced: they decide where a Button is, and focus and reading follow that place, so a screen-reader user meets the actions in the same order a sighted user sees them.
- The two flags describe the task, not the tone: in "Cancel · Delete" Delete is confirming. A destructive action is told apart by its own look (the Button's destructive) and its label, not by its place.
- Compose has no group role: the group is a traversal group whose content description is label, and each item keeps its own role.
- Text size: Buttons in a group grow as they do anywhere; a connected row stretches every item to the tallest, and a column makes every item as wide as the widest.
- A divider is decoration: it is not exposed, takes no press and is no boundary a person must find. What tells two joined Buttons apart for assistive technology is that each is its own button with its own name; for sight, their content and the line or the gap between them.
- A seam between a selected and an unselected toggle carries no line: the fill of the selected item ends there, at a contrast far above 3:1 against its neighbour, and that edge is the boundary. The selected state itself is exposed by the toggle (BTN-14), never by the line.
- In the default system the outline of an outlined group and its rules are the hairline (the divider's color), far below 3:1 against the surface. That is within WCAG 1.4.11: each Button is identified by its label or its icon, not by its edge.
Generado a partir de specs/button-group.spec.json, tokens/sys/measure.tokens.json
- Cada botón conserva su propio rol, nombre, estado y parada de tabulación. Tab los recorre en el orden de colocación: de inicio a fin en una fila, de arriba abajo en una columna. El grupo no recibe el foco, no gestiona las teclas de flecha y no guarda ninguna selección.
confirmingydismissiveno se anuncian. Deciden dónde está un botón, y el foco y la lectura siguen ese lugar.- Un divisor es decoración: queda oculto para la tecnología de apoyo y no recibe pulsaciones. Lo que distingue dos botones unidos es que cada uno es un botón propio con su propio nombre.
- Con una etiqueta, el grupo se expone como un grupo que recibe de ella su nombre y que contiene sus botones. Sin ella, no añade nada al árbol de accesibilidad.
- En un grupo connected cada botón tiene como mínimo el ancho de un objetivo, y su área de pulsación se extiende por encima y por debajo de la fila hasta el objetivo mínimo. En un grupo standard, una pulsación dentro de los límites visibles de un botón siempre va a ese botón, y una pulsación en la separación va al más cercano.
- El indicador de foco sigue la forma de cada botón, incluidas las esquinas unidas.
- Con tamaños de texto mayores, los botones crecen como en cualquier otro lugar. Una fila connected estira todos los botones hasta la altura del más alto y una columna hace que todos sean tan anchos como el más ancho, de modo que la forma unida se mantiene uniforme.
- En el sistema predeterminado, el contorno de un grupo outlined y sus divisores son la línea fina, que es discreta a propósito: cada botón se identifica por su etiqueta o su icono, no por su borde.
- Las compilaciones de depuración avisan de un grupo con menos de dos elementos, de contenido que no es un Button de Tanrei, de un botón con ambos indicadores, de una variante, una orientación o una separación desconocidas, de una fila de ancho completo cuyos botones no pueden repartirse el ancho a partes iguales, y de una fila que sigue sin caber después de que sus Buttons se hayan estrechado.
Carencias conocidas por plataforma:
- Web: un botón de icono connected tiene el ancho del objetivo del puntero con un puntero fino, como un ratón, y el del objetivo táctil con uno grueso. Cuando la forma propia de un elemento es una píldora, las esquinas que vuelve hacia un vecino son circulares en lugar de estar suavizadas por el suavizado de esquinas. Dentro de un grupo, la etiqueta de un Button puede ajustarse en varias líneas; fuera de él se mantiene en una línea. El aviso de una fila que sigue sin caber se comprueba cuando el grupo cambia de tamaño o cambian sus hijos, no cuando cambia la etiqueta de un elemento.
- SwiftUI no tiene rol de grupo. Un grupo con etiqueta es un contenedor que recibe su nombre de la etiqueta y contiene sus botones. Un Button cuyo título es una
LocalizedStringKey, como lo es un literal de cadena, puede estrecharse por debajo de su palabra más larga, que entonces se parte por dentro. El aviso de una fila que sigue sin caber es un fallo de aserción en las compilaciones de depuración, así que un diseño que es demasiado estrecho por un momento, como un contenedor cuyo ancho se anima, detiene una aplicación de depuración. - Compose no tiene rol de grupo. Un grupo con etiqueta es un grupo de recorrido cuya descripción de contenido es la etiqueta, y cada botón conserva su propio rol.
- React Native: cómo reparten iOS y Android una pulsación en la separación entre dos botones, los botones estirados de un grupo connected con tamaños de texto grandes, y si VoiceOver y TalkBack anuncian el nombre del grupo cuando el foco entra en él, no se han comprobado en un dispositivo.
- React Native: un Button estrechado puede encogerse por debajo de su palabra más larga, cuyas letras se parten entonces entre líneas, porque el diseño de React Native no tiene un ancho mínimo de contenido para el texto.
- En un grupo standard, las áreas de pulsación se encuentran en el centro de la separación. La separación del sistema predeterminado (16) es lo bastante ancha para que cada botón de icono, incluido el pequeño, conserve entero su objetivo táctil de 48 entre sus vecinos. Un sistema de diseño con una separación menor reduce los objetivos entre los botones.
- Da nombre a todos los botones de icono. La etiqueta del grupo no da nombre a los botones que contiene.
- Coloca el grupo donde corresponde a sus botones en el orden de lectura. La tecnología de apoyo llega a ellos uno por uno, en el orden en que aparecen.
- Si un conjunto de conmutadores debe ser mutuamente excluyente, usa un control de selección única: pestañas donde la elección cambia el contenido de debajo, botones de opción donde define un valor, o un botón segmentado donde el sistema lo incluya. Cada uno de ellos se anuncia como una sola elección; una fila de botones conmutadores no.