You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/eidos/components/banner
dev 376f9e33eb
feat(layout): Layout Batch 2 — aspect-ratio, auto-grid, banner, float
5 months ago
..
README.md feat(layout): Layout Batch 2 — aspect-ratio, auto-grid, banner, float 5 months ago
banner-close.svelte feat(layout): Layout Batch 2 — aspect-ratio, auto-grid, banner, float 5 months ago
banner.css feat(layout): Layout Batch 2 — aspect-ratio, auto-grid, banner, float 5 months ago
banner.svelte feat(layout): Layout Batch 2 — aspect-ratio, auto-grid, banner, float 5 months ago
index.ts feat(layout): Layout Batch 2 — aspect-ratio, auto-grid, banner, float 5 months ago
types.ts feat(layout): Layout Batch 2 — aspect-ratio, auto-grid, banner, float 5 months ago

README.md

Eidos Banner

Full-bleed announcement strip with intent / variant / size visual treatment. Renders as <header data-banner role="banner"> so the landmark works reliably across layouts — HTML spec only grants role="banner" to a <header> when it is a top-level child of <body>; nesting it inside <main>, <article> or <section> strips the role. The explicit role stabilises the announcement intent.

Superficie

<Banner intent="affirm" variant="soft" size="md">
  <span>Your changes have been saved.</span>
</Banner>

<!-- Dismissible: consumer owns the visibility state. -->
<script>
  let show = $state(true);
</script>
{#if show}
  <Banner intent="risk" variant="solid">
    <span>Connection lost — reconnecting…</span>
    <Banner.Close onclick={() => (show = false)} />
  </Banner>
{/if}

Baseline

Origen: air/components/layout/banner (rama morfo-runtime).

Air shipped a single-prop Banner — <header role="banner"> + a small spacing/padding recipe and no variants. The task brief asked for a full announcement primitive with intent variants and an optional dismiss button, modelled after Chakra / Mantine / MUI Alert. The current shape keeps air's landmark contract (explicit role="banner") and adds:

  • intent mapped to the canonical UIX ColorRole (8 values: primary | secondary | neutral | affirm | fulfill | risk | threat | loss).
  • variant from the shared ChipVariant vocab (soft | solid | outline | ghost).
  • size narrowed from canonical Size to sm | md | lg — smaller doesn't read; larger turns the strip into a hero (out of scope).
  • Optional Banner.Close part. The dismiss is composition-driven: the consumer wraps Banner in {#if show} and wires onclick — Banner itself does not own the visibility state. See "Decisiones" below.

Air's CSS prefix (air-banner) is dropped; selectors target [data-banner] directly. CSS variables renamed from --air-banner-* to --banner-* (public) and --_banner-* (internal recipe-local).

Comparativa

Capacidad UIX (eidos) Chakra UI Alert Mantine Alert MUI Alert
Intent / status vocabulary UIX 8 (primary, secondary, neutral, affirm, fulfill, risk, threat, loss) Chakra 4 (info, warning, success, error) Mantine 6 (blue, gray, red, … semantic colors) MUI 4 (success, info, warning, error)
Variants soft / solid / outline / ghost subtle / left-accent / top-accent / solid filled / light / outline / default / transparent standard / filled / outlined
Sizes sm / md / lg Single size + custom Theme spacing scale Single size + custom
Landmark role="banner" Sí (explicit) No (uses role="alert") No (role="alert") No (role="alert")
Dismissible Sí (Banner.Close composition) Sí (CloseButton slot) Sí (withCloseButton) Sí (onClose)
Icon slot Composition (consumer's <Icon>) AlertIcon shorthand icon prop icon prop
Title + description structure Composition (consumer's markup) AlertTitle / AlertDescription title prop AlertTitle
ARIA live region No — Banner is a landmark, not a live region role="alert" (assertive) role="alert" role="alert"
Responsive props Sí (ResponsiveProp on intent / variant / size) Sí (useColorMode) Sí (responsive sx) Sí (sx)

Decisiones

  • role="banner", not role="alert" — Banner is a landmark for page-level announcements (site header, persistent notice, system status bar). Transient feedback that interrupts the screen reader belongs to <Toast>, which already declares the proper role="status" / aria-live="polite" contract. Forcing role="alert" on a static strip would be aggressive overreach.
  • Composition, not boolean dismissible — pattern matches the rest of UIX (DatePicker, Dialog, Drawer all dropped *Button boolean props 2026-05-21). The consumer wraps Banner in {#if show} and includes <Banner.Close onclick={() => (show = false)} /> to render the dismiss button. Banner doesn't own visibility, doesn't reset its own state, doesn't fire a sema event when closed. The feedback_user_design_overrides_canon_vocabulary rule applies: a generic landmark strip doesn't carry an intrinsic commit semantic.
  • intent uses UIX 8-role vocab, not Chakra's 4 — keeps the vocabulary consistent with the rest of the system (SearchField, Toast, Toggle, etc. all use the same ColorRole). The mapping from conventional info / success / warning / error is documented in the intent canon (src/uix/intent.ts).
  • variant = ChipVariant — reuses the canonical 4-value vocab instead of inventing a new union per component (Chakra ships 4, Mantine 5, MUI 3 — none with the same names). The same vocab powers Chip / Tag / Avatar.
  • No icon / title / description slots — composition over configuration. The consumer puts an Icon + heading + body inside Banner's children; flex/gap from the recipe handles the spacing.
  • Close is eidos-only — meets the four rules in eidos/components/README.md (no behavior, no aria contract beyond the user-supplied aria-label, no event-target role, no keyboard beyond a plain <button>). The morfo doesn't declare Close as a part because the close is the consumer's onclick — Banner doesn't own a commit semantic.

Eventos Sema

Banner declara 0 eventos. Es una primitiva pasiva que conforma una superficie de anuncio. La dismissión, cuando aplica, vive en el handler onclick que el consumidor adjunta a <Banner.Close> — Banner no commite ni emerge. Eso evita inflar el contrato Morfo con un evento genérico que no aplica universalmente (banners persistentes, banners informativos sin dismiss, banners full-bleed sin botón). Misma justificación que Box / Flex / Grid: la primitiva no carga semánticas que no posee.

Gaps

Gap Disposición Detalle
role="alert" automatic when intent='threat' descartar Banner es landmark; Toast cubre el live-region case. Forzar role=alert sería confuso.
Icon slot dedicado descartar El consumidor compone <Icon> antes del texto; gap del recipe espacía.
AlertTitle / AlertDescription parts diferir Pendiente si surgen ≥2 casos donde la composición plana sea ergonómicamente pobre.
Animación de entrada/salida diferir Hoy se compone con transition:slide del consumidor o con <Collapsible>. Si llegan ≥2 casos donde Banner necesite owned motion, subir a Sema con la verb surge.
borderRadius shorthand implementar Hoy via style=. Posible añadirlo como --banner-radius token público; ya existe en el recipe pero sin prop.
Persistent dismissed-state (cookie / localStorage) descartar No es responsabilidad del componente de layout. El consumidor implementa la persistencia.
Banner.Close as a real morfo part with sema close-cancel commit diferir Si se decide que la dismissión es semántica universal, subir Close a Morfo + Sema. Hoy es composition only.

Referencias

Passive justification

Visual-only primitive (scope: ['eidos'] en el morfo). Una sola part Provider que emite el marker [data-banner] + data-intent + data-variant + data-size + role="banner". Sin estados internos, sin ARIA contracts dependientes de runtime, sin keyboard, sin commit semantics. La Close children es eidos-only — el consumidor handlea onclick para visibility. No hay nada que animar como verbo Sema; las transiciones de aparición/desaparición las orquesta el consumidor con {#if} + transitions de Svelte o componiendo con <Collapsible>.

Powered by TurnKey Linux.