ATOM
Components (registry)Actions

ButtonGroup

Buttons sit flush so related actions read as one control. Use for aligned toolbars, not free-floating CTAs.

Preview

Editorial

<!-- F12c editorial — non-derivable only. Review: Karen. -->

## Ejemplos

Aligned toolbar of related actions:

```tsx import { ButtonGroup, ButtonGroupSeparator } from '@/components/atoms/ButtonGroup'; import { Button } from '@/components/atoms/Button';

<ButtonGroup orientation="horizontal" aria-label="Document actions"> <Button variant="secondary" size="s">Edit</Button> <ButtonGroupSeparator /> <Button variant="secondary" size="s">Share</Button> <Button variant="primary" size="s">Publish</Button> </ButtonGroup> ```

## Accesibilidad

- Root exposes `role="group"` — always pass `aria-label` (or `aria-labelledby`) describing the set of actions. - Separators are `aria-hidden`; do not put meaning only in the divider.

### Correcto

- role='group' se agrega automaticamente al container - aria-label en el ButtonGroup describe el proposito del grupo - ButtonGroupSeparator tiene aria-hidden='true' — no se anuncia - Navegacion por teclado funciona: Tab mueve entre botones del grupo

### Evitar

- No anidar ButtonGroups — un solo nivel de agrupacion - No mezclar tamaños distintos en un mismo grupo — rompe la alineacion visual - No usar sin aria-label — el screen reader no puede describir el proposito del grupo

## Cuándo no usar

- Free-floating unrelated buttons in a page section — use spacing/layout, not `ButtonGroup`. - Single primary CTA — use one `Button`, not a group of one.

## Criterio de uso

- Agrupa acciones que operan sobre el mismo objeto o flujo y que necesitan leerse como una unidad. - Usa orientación horizontal en toolbars y vertical cuando el espacio o el contexto móvil exija acciones apiladas. - Pasa un nombre accesible al grupo; el agrupamiento visual no explica por sí solo la relación entre controles.

## Gotchas

- El grupo controla el colapso de bordes y radios. No añadas márgenes individuales entre hijos porque crearás separaciones inconsistentes. - Mantén el orden de acciones de menor a mayor compromiso y evita mezclar acciones sin relación sólo para ahorrar espacio. - **Nota**: Este CSS requiere que los botones hijos ya tengan sus estilos (Button o IconButton standalone CSS). ButtonGroup solo maneja el layout y radius collapse.

Uso

import { ButtonGroup } from '@/components/atoms/ButtonGroup';
import { Button } from '@/components/atoms/Button';

<ButtonGroup orientation="horizontal">
  <Button variant="secondary">Cancel</Button>
  <Button variant="primary">Save</Button>
</ButtonGroup>

Props

PropTipoDefaultRango / opcionesWhatHow
orientationselecthorizontal`horizontal`, `vertical`Layout axis of grouped buttons.horizontal for toolbars; vertical for stacked mobile actions. Default: horizontal.

Gotchas

  • layout

    Children must be Button/IconButton siblings — spacing is owned by the group, not ad-hoc margins.

Anatomía CSS

<div class="button-group">
  <span class="button-group__separator"></span>
  <span class="button-group__text"></span>
</div>
ClasePropósito
button-grouproot
button-group--verticalmodifier
button-group__separatorelement
button-group__textelement

Tokens resueltos

Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.

VariantePropValorToken
allbg#f5f5f5muted
allfg#525252muted.foreground
allborder1pxstroke.hairline

CSS mínimo funcional

Autocontenido: sin imports ni tokens. Para previews y prototipos — en producción se consume el CSS del DS (@atom-uikit/css/components.css</code>, <code>@atom-uikit/tokens/tokens.css).

/* -------------------------------------------------------------------------
   Button Group

   Joins adjacent buttons into a cohesive unit.
   Removes inner radius, collapses inner borders.

   Orientation: horizontal (default), vertical
   Parts: .button-group, .button-group__separator, .button-group__text
   ------------------------------------------------------------------------- */

/* ---- Container ---- */

.button-group {
  display: inline-flex;
  align-items: stretch;
}

.button-group--vertical {
  flex-direction: column;
}

/* ---- Horizontal: radius + border collapse ---- */

.button-group:not(.button-group--vertical) > .button,
.button-group:not(.button-group--vertical) > .icon-button {
  border-radius: 0;
}

.button-group:not(.button-group--vertical) > .button:first-child,
.button-group:not(.button-group--vertical) > .icon-button:first-child {
  border-radius: 8px 0 0 8px;
}

.button-group:not(.button-group--vertical) > .button:last-child,
.button-group:not(.button-group--vertical) > .icon-button:last-child {
  border-radius: 0 8px 8px 0;
}

.button-group:not(.button-group--vertical) > .button + .button,
.button-group:not(.button-group--vertical) > .icon-button + .icon-button,
.button-group:not(.button-group--vertical) > .button + .icon-button,
.button-group:not(.button-group--vertical) > .icon-button + .button {
  margin-left: calc(-1 * 1px);
}

/* ---- Vertical: radius + border collapse ---- */

.button-group--vertical > .button,
.button-group--vertical > .icon-button {
  border-radius: 0;
  width: 100%;
}

.button-group--vertical > .button:first-child,
.button-group--vertical > .icon-button:first-child {
  border-radius: 8px 8px 0 0;
}

.button-group--vertical > .button:last-child,
.button-group--vertical > .icon-button:last-child {
  border-radius: 0 0 8px 8px;
}

.button-group--vertical > .button + .button,
.button-group--vertical > .icon-button + .icon-button,
.button-group--vertical > .button + .icon-button,
.button-group--vertical > .icon-button + .button {
  margin-top: calc(-1 * 1px);
}

/* ---- Separator ---- */

.button-group__separator {
  align-self: stretch;
  background-color: #e5e5e5;
}

.button-group:not(.button-group--vertical) > .button-group__separator {
  width: 1px;
}

.button-group--vertical > .button-group__separator {
  height: 1px;
}

/* ---- Text ---- */

.button-group__text {
  display: inline-flex;
  align-items: center;
  padding: 0 12px;
  font-family: 'inter tight', -apple-system, blinkmacsystemfont, 'segoe ui', roboto, helvetica, arial, sans-serif, ui-sans-serif, system-ui, sans-serif;
  font-size: 12.8px;
  font-weight: 500;
  color: #525252;
  white-space: nowrap;
  border: 1px solid #e5e5e5;
  background-color: #f5f5f5;
}

Codigo fuente

components-react
components/atoms/ButtonGroup.tsx
import { forwardRef, type ReactNode } from 'react';

function cn(...classes: (string | false | undefined | null)[]) {
  return classes.filter(Boolean).join(' ');
}

/* ---- Group ---- */

export type ButtonGroupProps = {
  orientation?: 'horizontal' | 'vertical';
  'aria-label'?: string;
  children: ReactNode;
  className?: string;
};

export const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>(
  ({ orientation = 'horizontal', children, className, ...props }, ref) => {
    return (
      <div
        ref={ref}
        role="group"
        className={cn('button-group', orientation === 'vertical' && 'button-group--vertical', className)}
        {...props}
      >
        {children}
      </div>
    );
  },
);

ButtonGroup.displayName = 'ButtonGroup';

/* ---- Separator ---- */

export type ButtonGroupSeparatorProps = {
  className?: string;
};

export function ButtonGroupSeparator({ className }: ButtonGroupSeparatorProps) {
  return <div aria-hidden="true" className={cn('button-group__separator', className)} />;
}

/* ---- Text ---- */

export type ButtonGroupTextProps = {
  children: ReactNode;
  className?: string;
};

export function ButtonGroupText({ children, className }: ButtonGroupTextProps) {
  return <span className={cn('button-group__text', className)}>{children}</span>;
}

Webflow

Webflowbutton-group

Pega en el Designer como application/json, luego convierte a Component (Atom / ButtonGroup) y publica. Formato interno no documentado de Webflow — regenerable, no dependencia de runtime.

Setup del sitio (una vez)

Custom Code → Head:

<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/tokens.css">
<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/components.css">

Unsupported (no silencioso)

  • selector on .button-group:not(.button-group--vertical) > .button, .button-group:not(.button-group--vertical) > .icon-buttoncompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .button-group:not(.button-group--vertical) > .button:first-child, .button-group:not(.button-group--vertical) > .icon-button:first-childcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .button-group:not(.button-group--vertical) > .button:last-child, .button-group:not(.button-group--vertical) > .icon-button:last-childcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .button-group:not(.button-group--vertical) > .button + .button, .button-group:not(.button-group--vertical) > .icon-button + .icon-button, .button-group:not(.button-group--vertical) > .button + .icon-button, .button-group:not(.button-group--vertical) > .icon-button + .buttoncompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .button-group--vertical > .button, .button-group--vertical > .icon-buttoncompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .button-group--vertical > .button:first-child, .button-group--vertical > .icon-button:first-childcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .button-group--vertical > .button:last-child, .button-group--vertical > .icon-button:last-childcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .button-group--vertical > .button + .button, .button-group--vertical > .icon-button + .icon-button, .button-group--vertical > .button + .icon-button, .button-group--vertical > .icon-button + .buttoncompound/descendant selector — moved to head Custom Code (Designer styles are single-class)

Tras pegar: Create component → nombre Atom / ButtonGroup → Publish.

Componentes relacionados

On this page

Detalles

Publicado14 de mayo de 2026
Categoriaactions
Lectura...
Visitas...
Ayuda?Slack

Componente

ButtonGroup

Source

components-react / cssDisponible via MCP: atom_uikit_source("button-group")
Abrir en Storybook