Componentes · Ola 2 · Superposiciones
Popover
Contenido no modal anclado a un botón: filtros, detalles o un formulario corto. Se abre con un clic, se cierra con Escape o con un clic afuera y devuelve el foco al botón que lo abrió.
import { Popover } from '@archeblack/ui';
Ejemplos
Básico
title se ve arriba y nombra al diálogo. El contenido admite texto y links.
<script lang="ts">
import { Button, Popover } from '@archeblack/ui';
import { IconInfoCircle } from '@archeblack/ui/icons';
</script>
<!-- title nombra al diálogo y se ve arriba del contenido. -->
<Popover title="Uso del plan">
{#snippet trigger(props)}
<Button {...props} iconStart={IconInfoCircle}>Detalles</Button>
{/snippet}
<p>Llevas 8,4 GB de 10 GB. La cuota se renueva el 1 de octubre.</p>
<p>
<Button href="#props" variant="link">Ver el detalle del uso</Button>
</p>
</Popover> Filtros
Controles adentro y bind:open para cerrarlo desde «Aplicar». Al abrirse, el foco pasa al primer control; al cerrarse, vuelve al disparador.
<script lang="ts">
import { Button, Checkbox, Popover, Switch } from '@archeblack/ui';
import { IconFilter } from '@archeblack/ui/icons';
let open = $state(false);
let failed = $state(true);
let production = $state(false);
let preview = $state(true);
const active = $derived([failed, production, preview].filter(Boolean).length);
</script>
<!--
Con bind:open el producto cierra el popover desde adentro («Aplicar»); el foco vuelve al
disparador. Al abrirse, el foco pasa al primer control.
-->
<Popover title="Filtros" align="start" bind:open>
{#snippet trigger(props)}
<Button {...props} iconStart={IconFilter}>Filtros · {active}</Button>
{/snippet}
<Switch label="Solo con errores" bind:checked={failed} />
<div style:display="grid" style:gap="var(--arche-spacing-1)">
<Checkbox label="Producción" bind:checked={production} />
<Checkbox label="Vista previa" bind:checked={preview} />
</div>
<div style:display="flex" style:justify-content="flex-end" style:gap="var(--arche-spacing-2)">
<Button
variant="ghost"
size="sm"
onclick={() => {
failed = production = preview = false;
}}
>
Limpiar
</Button>
<Button variant="primary" size="sm" onclick={() => (open = false)}>Aplicar</Button>
</div>
</Popover> Con flecha
arrow dibuja una flecha hacia el disparador; side elige el lado. Sin título visible, aria-label nombra al diálogo.
<script lang="ts">
import { Button, Popover } from '@archeblack/ui';
import { IconHelp } from '@archeblack/ui/icons';
</script>
<!-- Sin título visible: aria-label nombra al diálogo. -->
<Popover side="right" arrow aria-label="Qué es un dominio verificado">
{#snippet trigger(props)}
<Button {...props} variant="ghost" icon={IconHelp} aria-label="Qué es un dominio verificado" />
{/snippet}
<p>Un dominio verificado apunta a tu proyecto y tiene su certificado al día.</p>
</Popover> Props
| Prop | Descripción |
|---|---|
trigger Snippet<[TriggerAttributes]> | El disparador, obligatorio: el popover se ancla a él. Recibe los atributos del disparador (id, aria-haspopup="dialog", aria-expanded, aria-controls, el clic y la referencia), que se esparcen en un Button: {#snippet trigger(props)}<Button {...props}>Filtros</. |
children Snippet | El contenido. Admite controles, links y texto. |
title? string | Título visible, en texto fuerte. Nombra al diálogo (aria-labelledby). Sin title, aria-label o aria-labelledby son obligatorios. |
side? 'top' | 'right' | 'bottom' | 'left' Por defecto 'bottom' | Lado del disparador en el que aparece. Si no entra, pasa al opuesto. |
align? 'start' | 'center' | 'end' Por defecto 'center' | Alineación sobre el lado elegido. |
arrow? boolean Por defecto false | Dibuja una flecha que apunta al disparador. |
bind:open? boolean Por defecto false | Si el popover está abierto. Con open = false el producto lo cierra desde adentro y el foco vuelve al disparador. |
onOpenChange? (open: boolean) => void | Se llama cada vez que se abre o se cierra por sí mismo (disparador, Escape, clic afuera o el foco que sale), con el estado nuevo. No se llama cuando el producto cambia open. Llega después de actualizar open y solo si el cambio se aceptó: si un bind:open de función lo rechaza, no se llama. |
class? ClassValue | Clases del producto; se suman a arche-popover. |
...rest HTMLAttributes<HTMLDivElement> | Cualquier otro atributo va al <div role="dialog">, como aria-label o aria-labelledby. El rol lo decide el componente y title es el título visible. |
Accesibilidad
- El contenido es un
<div role="dialog">no modal, con nombre obligatorio por tipos:titlevisible,aria-labeloaria-labelledby. - El disparador lleva
aria-expanded,aria-controlsyaria-haspopup="dialog", y se abre y se cierra con Enter, espacio o un clic. - Al abrirse, el foco pasa al primer control del contenido. Si no tiene controles, el foco va al contenido, que muestra el aro de foco cuando se abrió con el teclado. No queda atrapado: Tab puede salir, y el resto de la página sigue disponible.
- Se cierra con Escape, con un clic afuera o al volver a pulsar el disparador. El foco vuelve al disparador.
- La superficie es
surface-overlaycon la elevación de superposición (borde, sombra y brillo tenue). En colores forzados lleva un borde del sistema. - Con movimiento reducido, aparece y desaparece sin escala ni fundido.
Qué evitar
- Un popover para una decisión que bloquea («¿Eliminar el proyecto?»). Un
Dialog: es modal, atrapa el foco y pide una respuesta. - Solo texto que describe un botón. Un
Tooltip, que se abre con el puntero y el foco sin mover el foco. - Una lista de acciones dentro de un popover. Un
Menu: navegación con flechas, typeahead y el rol correcto. - Formularios largos o con varios pasos. Un
Sheeto una página propia. - Abrir el popover con hover. Abrirlo con un clic: el contenido interactivo tiene que poder alcanzarse con el teclado y en pantallas táctiles.