9.2 KiB
Eidos Tabs
Visual wrapper over Soma Tabs. Eidos adds size, variant, collapse and motion presentation; Soma owns value, activation mode, roving focus, ARIA, presence and event emission.
Baselines
| Platform | Surface | Comparison |
|---|---|---|
Air (glm-5) |
Tabs, List, Trigger, Content; sizes xs-lg, variants line/surface/pills, mobile vertical collapse below md, behavior cue on value change. |
Covered. Eidos keeps the same variants and size range, generalizes collapse through collapseAt, and exposes motion through CSS/data attrs. |
| Radix Tabs | Root, List, Trigger, Content; controlled/uncontrolled, orientation, dir, activation mode, list loop, forceMount. |
Covered by Soma/Eidos. Loop lives on root instead of list. |
| Bits UI Tabs | Root, List, Trigger, Content; bindable value, automatic/manual activation, force-mounted transitions. |
Covered. Eidos keeps transitions declarative with motionPreset and content keepMounted. |
| Ark UI Tabs | Root, List, Trigger, Content, Indicator; activation mode, deselectable, lazyMount, unmountOnExit, custom ids. |
Indicator is covered. deselectable is intentionally not supported; tabs remain one-of-many. lazyMount/unmountOnExit map to Soma default plus forceMount/keepMounted. |
| shadcn-svelte Tabs | Styled Bits/Radix-like Root/List/Trigger/Content. | Covered with first-party tokens and compound namespace. |
| React Aria Tabs | Tabs, TabList, Tab, TabPanel; orientation, disabled keys, controlled selection and accessibility relations. |
Covered with per-trigger disabled and value-based trigger/content registry. |
Sources: Radix, Bits UI, Ark UI, shadcn-svelte, React Aria.
Anatomy
<script lang="ts">
import { Tabs } from '$uix/eidos/components/tabs';
let value = $state('account');
</script>
<Tabs bind:value variant="line" size="md">
<Tabs.List aria-label="Account sections">
<Tabs.Trigger value="account">Account</Tabs.Trigger>
<Tabs.Trigger value="security">Security</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="account">Account panel</Tabs.Content>
<Tabs.Content value="security" keepMounted>Security panel</Tabs.Content>
</Tabs>
Decisions
| Gap / Choice | Decision |
|---|---|
Air mobile collapse was hard-coded to md |
Eidos uses `collapseAt: 'never' |
Ark deselectable |
Not supported. Tabs are a required one-of-many content selector in UIX; deselection belongs to disclosure/toggle patterns. |
Ark lazyMount / unmountOnExit |
Soma default is lazy/unmounted inactive content. keepMounted aliases Soma forceMount for consumers that need persistent panels. |
| Ark/animated indicator | Implemented as Tabs.Indicator; measurement is visual-only in Eidos, while the part marker remains declared in Morfo/Soma. |
| Tablist accessible name | Tabs.List requires consumer-provided aria-label or aria-labelledby; Soma warns in dev instead of inventing a generic label. |
Visual Props
| Prop | Values | Effect |
|---|---|---|
size |
xs | sm | md | lg |
Trigger height, padding and content spacing. |
variant |
line | surface | pills |
Underline, card/surface, or pill-track treatment. |
fitted |
boolean |
Triggers take equal width. |
collapseAt |
'never' | breakpoint | px number |
Vertical tabs collapse responsively below threshold. |
scrollable |
boolean |
Allows overflow scrolling on the list. |
motionPreset |
none | fade | slide |
Content transition recipe. |
Ownership
- Morfo declares tab/list/trigger/content/indicator roles, states and ARIA relationships.
- Soma owns value, keyboard navigation, activation mode, trigger/content ID
registries, disabled handling, presence and the indicator's MEASUREMENT
(
MeasuredIndicator, exposed as--indicator-{x,y,w,h}). - Eidos owns size/variant/collapse/scroll/motion presentation, and positions + shapes the indicator from those vars — the "soma positions, eidos shapes" contract. The measurement used to live in the eidos wrapper; it moved to soma so it re-measures on an RTL flip, which a wrapper with no access to the resolved direction could not do.
Talla y tema
79 claves públicas en lib/recipes/base.ts, y la pestaña Tokens de su
demo las resuelve en vivo sobre el escenario. El reparto, por dónde pintan:
| grupo | claves | notas |
|---|---|---|
| Pila del provider | stack-gap, line-stack-gap |
el hueco lista↔panel; line lo aprieta |
Marco surface |
provider-{radius,border-width,border,bg}, surface-provider-{padding,shadow} |
sólo bajo variant='surface' |
| Lista | list-{gap,padding,radius,width,bg,border-width,border}, line-list-padding-inline-end, surface-list-bg, pills-list-bg, segmented-list-{bg,radius} |
list-width es el raíl VERTICAL |
| Trigger, por talla | trigger-height-{xs,sm,md,lg}, trigger-padding-inline-{…}, trigger-gap-{…}, trigger-font-size-{…} |
cada uno al bundle --size-{k}-* |
| Trigger, cromo | trigger-{radius,border-width,bg,border,fg}, trigger-font-{family,weight}, trigger-line-height, trigger-letter-spacing, hover-trigger-fg, active-trigger-{fg,bg,border}, pills-trigger-radius, segmented-trigger-radius, active-{surface,pills}-trigger-border, active-pills-trigger-shadow, active-segmented-trigger-{bg,fg,shadow} |
el modificador va DELANTE (D-TH.6) |
| Foco y estado | trigger-focus-ring-{offset,width,color}, disabled-opacity |
el trío de foco ESPEJA la familia del sistema — exento de la gramática de tinta |
| Panel | content-padding-{xs,sm,md,lg}, content-{radius,bg,border-width,border,fg}, surface-content-{border,bg}, pills-content-padding-block-start |
|
| Indicador | indicator-transition-{duration,ease}, indicator-line-thickness |
la FORMA y el deslizamiento |
| Movimiento | trigger-transition-{duration,ease}, scrollable-fade |
Lo que el eje dejó fuera el 2026-08-24 (96 % → 97 %), medido y escrito:
--indicator-{x,y,w,h}NO se acuñan. Son el canal de VALOR de soma: la capaMeasuredIndicatorescribe el rect del trigger activo en cada movimiento. Un público encima sería un knob que un tema podría fijar, y fijarlo rompe el deslizamiento — la misma adjudicación quenavigation-menuya lleva para su propioindicator-w.width: 100%defittedes identidad, no knob:fittedES «la lista llena el eje inline». Firmado con su anotaciónliteral:.
Lo que el guard R-5.4 da por silencioso (70/79 mueven; 9 adjudicados en
scripts/theming-sentinel-exceptions.ts, todos medidos a mano): los cuatro
tokens de transición —el guard CONGELA transition para poder medir todo lo
demás—, los tres del anillo de foco —lo apaga con un blur a propósito—,
trigger-bg —la demo monta el Indicator, y la regla que sube la etiqueta por
encima de la píldora gana (0,2,0)— y scrollable-fade, que además pinta por
mask-image, fuera del juego de propiedades que el guard lee.
Baseline
Referencia local: Soma + Morfo + Eidos. La capa visual de Eidos expone size / variant / color sobre el comportamiento que Soma ya implementa; el morfo declara parts, ARIA, keyboard y eventos sema. Las decisiones se alinean con las bibliotecas externas listadas en la Comparativa.
Comparativa
| Capacidad | Bits UI | Ark UI | React Aria | shadcn-svelte | UIX |
|---|---|---|---|---|---|
| Surface compound (root + parts) | ✓ | ✓ | ✓ | ✓ | ✓ |
| ARIA semantics + keyboard nav | ✓ | ✓ | ✓ | parcial | ✓ via morfo |
| Variantes visuales (size / variant) | parcial | parcial | n/a | tailwind | ✓ recipe |
| Form integration | parcial | parcial | ✓ | parcial | ✓ via Field |
| Eventos sema declarativos | n/a | n/a | n/a | n/a | ✓ |
Decisiones
- El wrapper Eidos se mantiene fino: ergonomía visual + tokens. Comportamiento y ARIA viven en Soma + Morfo.
- Strings públicos usan idlangref bajo
components.{kebab}.*, con catálogo ensrc/uix/langs/components/{kebab}.ts. - Eventos sema declarados en el morfo cubren los cambios de estado que cargan peso perceptivo (commit, signal). Las navegaciones internas por teclado son focus moves, no eventos.
- Cuando la API necesita comportamiento nuevo, va a Soma / Morfo primero; Eidos sólo expone la receta visual del resultado.
Gaps
| Gap | Disposición | Detalle |
|---|---|---|
| API extendida sobre las referencias externas | diferir | Sólo si llega un caso de uso real. Mantener la superficie estable. |
| Cobertura adicional de variantes visuales | diferir | El recipe cubre sm/md/lg + solid/outline/ghost. Más variantes requieren caso concreto. |
| Documentación per-prop exhaustiva | implementar | Cuando se cierre el ciclo de remediación de cada componente. |
| Tests browser-level del flujo completo (Playwright) | implementar | Cobertura visual + interacciones. Se hace en una pasada conjunta de tests. |