Saltar al contenido
Librería Devs Figma ↗

Handoff para devs · SOFIA

SOFIA Librería Devs

Componentes de Bottom Sheet, Modal y Side Sheet de SOFIA — el asistente conversacional de Despegar — implementados en HTML + CSS sobre EVA.

  • 45 variantes
  • 7 grupos
  • 3 animaciones JS

Instalación

Cargá EVA, Rubik, los tokens SOFIA y el CSS del componente que uses. Cada card muestra su CSS requerido exacto.

HTML · <head>
<!-- 1. EVA -->
<link rel=”stylesheet” href=”https://www.despegar.com/eva-core/static/eva/eva-core.min.css?”>
<link rel=”stylesheet” href=”https://www.despegar.com/eva-core/static/eva/eva.min.css?”>
<!-- 2. Rubik -->
<link href=”https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600;700&display=swap” rel=”stylesheet”>
<!-- 3. Tokens + base SOFIA -->
<link rel=”stylesheet” href=”css/tokens.css”>
<link rel=”stylesheet” href=”css/base.css”>
<!-- 4. CSS del componente (ver “CSS requerido” en cada card) -->
<link rel=”stylesheet” href=”css/components/bs-header.css”>

Bottom Sheet

Definición (Figma · “botonsheet”)

Es una superficie que emerge desde el borde inferior de la pantalla y se apoya sobre el contenido principal, sin reemplazarlo. Convive con la vista de origen en lugar de navegar a una nueva ruta: el contexto queda visible (o al menos implícito) detrás de un scrim, y la superficie se puede descartar deslizando hacia abajo, tocando el fondo o cerrando con la X.

Cuándo / para qué
  • Contextual, acciones rápidas, listas, formularios cortos
  • opciones, filtros, detalles, acciones contextuales.

Plataforma: Mobile

Playground · Bottom Sheet

Abrir ↗

Bottom Sheet · Estructura base (Header, Overlay, Content)

Piezas base para armar cualquier bottom sheet de SOFIA: el header (BS_header, con 4 variantes según la propiedad Handle), el overlay que oscurece el chat detrás del sheet y el área de contenido con el placeholder BS / Slot. El header es de ancho fluido: se usa igual en el bottom sheet mobile (360px) y en el side sheet desktop (421px).

Sección Figma 49:79 · CSS: css/components/bs-header.css · 12 variantes · grupo bs-header

BS_header · Handle=Title

bs-header/header-title360 × 56px · stage #d8d8d8
Handle=Title

Header por defecto: handle de arrastre + título + botón cerrar. Usalo cuando el sheet solo necesita nombrar su contenido (formularios, detalle, confirmaciones). Alto 56px.

Notas de implementación
Título con la clase EVA .eva-3-h5 (Subtitle L · Rubik Medium 16/20, Neutral-800). Cerrar = ícono EVA eva-3-icon-close a 24px dentro de un contenedor de 16×16 (en Figma, en esta variante el contenedor mide 16 y en las demás 20). El stroke Borders/medium (izq/der/arriba) se dibuja con ::after para no desplazar el contenido, igual que el stroke inside de Figma. Handle 40×4 color --sofia-bs-handle-color (#EBE5F5); el MD de referencia menciona Neutral/400 pero en Figma el handle usa #EBE5F5. Stage gris solo para que se vean las esquinas redondeadas (radio 20px).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css

BS_header · Handle=Slot

bs-header/header-slot360 × 88px · stage #d8d8d8
Handle=Slot

Header con título + cerrar y una fila libre ("Content / Slot", 32px de alto) debajo. Usalo cuando el header necesita contenido propio además del título (header de marca, buscador, resumen). Reemplazá el placeholder .sofia-slot por tu contenido. Alto 88px.

Notas de implementación
El slot es el placeholder BS / Slot (fill rgba(146,116,217,.2), radio 4px, texto Inter Regular 14 #6633CC); en producción se reemplaza por contenido real. Separación título → slot 12px.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

BS_header · Handle=Tabs

bs-header/header-tabs360 × 114px · stage #d8d8d8
Handle=Tabs

Header con título + cerrar y una barra de 2 tabs con título y bajada ("Titulo" / "Bajada"). Usalo cuando el sheet tiene secciones del mismo nivel entre las que el usuario alterna sin salir del sheet (por ejemplo, opciones de ida y vuelta). El contenido de cada tab va en el Content del sheet. Alto 114px.

Notas de implementación
Tabs = componente EVA .eva-3-tabs -no-stroke con ítems -subtitle (2 .tab-label); el panel .tabs-container se omite porque en Figma el Swap-Content está oculto y el contenido vive en el Content del sheet. Ajustes mínimos documentados en el CSS: alto del ítem 64px (EVA 52px) y text-align:left (EVA centra). El indicador activo es el de EVA (80% del ancho del tab, 128px) y en Figma mide ~136px. Esta variante no tiene stroke en Figma. El cambio de tab (-active / aria-selected) lo maneja la app.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css

BS_header · Handle=Icon Only (tags)

bs-header/header-tags360 × 84px · stage #d8d8d8
Handle=Icon Only

Header con título + cerrar y una fila de tags ("Tag text") con scroll horizontal. Usalo cuando el sheet muestra resultados que se pueden filtrar rápido (tags de EVA). El último tag queda cortado a propósito para indicar que hay más. Alto 84px.

Notas de implementación
En Figma la propiedad se llama "Icon Only" pero la variante muestra la fila de tags (anotación "Header con tags"). Tags = EVA .eva-3-tag (Light, Default, no seleccionado; incluye el tag-icon cross que EVA muestra solo en -active). Ajustes mínimos: alto 32px (EVA 36px), margin 0 (el gap de 8px lo da el contenedor) y padding horizontal 15px: EVA suma 1px de borde al padding de 16px y en Figma el stroke es interior, así cada tag mide 87px como en Figma. Son los mismos ajustes que usa la fila de tags del hero (bs-hero).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css

BS_header · Type=mobile-compact

bs-header/header-mobile-compact360 × 48px · stage #d8d8d8
Platform=MobileType=mobile-compact

Header compacto mobile de la Librería SOFIA: handle gris (Neutral-400) + fila título + cerrar SVG 16×16. Sin área de contenido debajo. Alto 48px. Usarlo en sheets que no necesitan preview de contenido en el header.

Notas de implementación
El color del handle en la Librería SOFIA es #C0C4CC (Neutral-400), distinto al #EBE5F5 de las variantes del handoff original. El botón cerrar usa el SVG propio de SOFIA (16×16, fill #323439), no el ícono EVA de 24px.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css

BS_header · Type=mobile

bs-header/header-mobile360 × 97px · stage #d8d8d8
Platform=MobileType=mobile

Header mobile con handle + título + cerrar + área de contenido (BS/Slot) debajo. El slot es el placeholder para contenido propio del header (buscador, resumen, selector). Alto 97px.

Notas de implementación
Mismas diferencias de handle color que en mobile-compact. El slot tiene 37px de alto dentro del header.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

BS_header · Type=desktop-compact

bs-header/header-desktop-compact575 × 56px · stage #d8d8d8
Platform=DesktopType=desktop-compact

Header compacto desktop (side sheet / panel lateral): handle gris + título Title S (20px) + cerrar SVG 16×16. Sin área de contenido. Alto 56px · Ancho 575px.

Notas de implementación
El título usa Title S (H4 · Rubik Medium 20/28), más grande que en mobile (Subtitle L 16px). La clase CSS .sofia-bs-header__title--s aplica ese size. El ancho de 575px es el del panel desktop en la Librería SOFIA.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css

BS_header · Type=desktop

bs-header/header-desktop575 × 105px · stage #d8d8d8
Platform=DesktopType=desktop

Header desktop con título Title S + cerrar + BS/Slot de contenido. El contenedor del handle tiene 12px de alto pero está vacío (sin barra visible). Alto 105px · Ancho 575px.

Notas de implementación
El handle container existe (reserva 12px de espacio) pero no tiene barra visible en Figma. Se implementa con .sofia-bs-header__handle--empty (sin span interior). El slot tiene ~37px de alto.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

BS_header · Type=start_pack_M

bs-header/start-pack-m360 × 48px · stage #d8d8d8
Platform=MobileType=start_pack_M

Header mobile de inicio de viaje: handle + "Tu próximo viaje" + fila de íconos (globe, plane, hotel) + chevron-down. Muestra los 3 verticales de SOFIA con íconos custom de 16×16. Alto 48px.

Notas de implementación
Los íconos globe, plane y hotel son SVGs propios de SOFIA DS (16×16, fill #72777F = Neutral-500). No existen en EVA. El chevron-down sí es EVA (eva-3-icon-chevron-down). El handle tiene el color gris de la librería (#C0C4CC).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css

BS_header · Type=start_pack_D

bs-header/start-pack-d575 × 56px · stage #d8d8d8
Platform=DesktopType=start_pack_D

Versión desktop del header de inicio de viaje: título Title S + íconos de verticales + chevron. Sin barra de handle visible (mismo patrón que header-desktop). Alto 56px · Ancho 575px.

Notas de implementación
Mismo comportamiento que start_pack_M pero con tipografía Title S y sin handle bar visible (handle container vacío).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css

Overlay

bs-header/overlay360 × 720px · stage #ffffff

Fondo oscuro que va detrás del bottom sheet (y de modales/side sheets) para bloquear la conversación. Cubre todo el viewport; el tamaño varía según el dispositivo, lo que hay que respetar es el color.

Notas de implementación
Fill BS/bg/overlay = --sofia-bs-overlay-color (#323439). En Figma se ve opaco (sin opacidad de capa); el MD de referencia menciona overlay/opacity 0.8 pero no está aplicado en el nodo, y js/motion/bottom-sheet.js anima la opacidad del overlay de 0 a 1. En el snippet usa position:absolute; inset:0 respecto del contenedor posicionado; para pantalla completa usar position:fixed.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-header.css
  • css/components/side-sheet.css

Content · BS / Slot

bs-header/content-slot360 × 396px · stage #ffffff

Área de contenido del bottom sheet (scrolleable) con el placeholder BS / Slot. Va entre el header y el footer (Action Container). Reemplazá .sofia-slot por el contenido real del sheet.

Notas de implementación
Padding 16px vertical (--sofia-ss-spacing-action-v) y 20px horizontal. El alto es 396px por defecto (variable --sofia-bs-content-height) y se puede sobreescribir según la altura del sheet. En Figma el Slot mide 326px dentro de un Content de 360 con padding 20 (desborda 3px por lado); se replica con width calc(100% + 6px). El texto del placeholder usa Inter Regular 14 como en Figma.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Bottom Sheet · Header con imagen (BS_imageBG_tittle)

Header hero con foto a sangre, degradado/blur sobre la imagen, título + bajada, handle y botón cerrar circular. Se usa en sheets de producto/paquete que arrancan con una imagen full-bleed. Tiene ancho fluido: el mismo componente se usa en el bottom sheet mobile (360px) y en el side sheet desktop (421px); el alto del hero es siempre 240px y la foto cubre con object-fit: cover.

Sección Figma 49:79 · CSS: css/components/bs-hero.css · 3 variantes · grupo bs-hero

BS_imageBG_tittle · Header=Title

bs-hero/hero-title360 × 240px · stage #ffffff
Header=Title

Solo hero (240px): foto de fondo, scrims con blur, "Titulo" (Title L) + "Bajada" (Body M) en blanco, handle arriba al centro y botón cerrar circular arriba a la derecha. Reemplazá el src de la foto por la imagen del producto.

Notas de implementación
Blur/degradado: en Figma son dos vectores (Rectangle 40 abajo, 360x106 centrado, y Rectangle 38 detrás del texto, forma espejada) con background blur + layer blur. Cada uno se implementa con dos capas: (1) backdrop-filter en CSS (blur 20px / 10px, los valores del SVG exportado) enmascarado con mask-image = el mismo path de Figma con el mismo feGaussianBlur (25 / 8) para que el borde del blur sea difuso como en Figma; (2) el SVG de Figma tal cual para el relleno negro difuminado (bs-hero-scrim-bottom.svg / bs-hero-scrim-text.svg). El backdrop-filter va en CSS porque dentro de <img> el foreignObject del SVG no ve la foto. __media lleva clip-path: inset(0): sin eso Chrome ignora el mask-image de un backdrop-filter cuando un ancestro tiene border-radius + overflow (el sheet redondeado). Rectangle 39 (4048:4026) queda fuera del frame (left 422px) y con 1% de opacidad: no es visible y no se incluye. Reutilización en side sheet (421px, nodos 4121:2238/2257/2296): mismo markup sin el span .sofia-bs-hero__handle (oculto en Figma) y con --sofia-bs-hero-radius: 0; la foto escala en % y el texto mantiene 320px como en Figma. Cerrar usa el ícono EVA eva-3-icon-close (equivalente al 'cross' de Figma). Diferencias residuales: la textura del blur de Chrome es algo más uniforme que el render de Figma (zona inferior ~5-10% más clara); 'Bajada' queda 1px más arriba por métricas de la fuente Rubik web. El letter-spacing usa los tokens en em (-0.002em / 0.0005em), que dan exactamente los -0.064px / 0.007px de Figma.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-hero.css
  • css/components/side-sheet.css

BS_imageBG_tittle · Header=Tabs

bs-hero/hero-tabs360 × 308px · stage #ffffff
Header=Tabs

Hero de 240px + fila de 68px con tabs EVA (.Tab Container Mobile, sin stroke, 2 tabs con subtítulo "Titulo" / "Bajada", la primera seleccionada). Al cambiar de tab se mueve la clase -active (y aria-selected) al <li> correspondiente, como en EVA.

Notas de implementación
Tabs → EVA eva-3-tabs -no-stroke con ítems -subtitle. Ajustes mínimos (los mismos que en el header con tabs de bs-header, para que ambas piezas se vean iguales): alto del ítem 64px (EVA 52px) con padding 16/12 y text-align:left (EVA centra), como el .Tab Container de Figma. EVA prevalece en el indicador activo (80% del ancho del tab, ~130px; en Figma ~139px), el divisor y los colores. El panel de contenido (.Swap-Content) está oculto en Figma, por eso no se incluye .tabs-container. La fila mantiene el alto de 68px y el padding 10/16 de Figma; el ítem de 64px se centra verticalmente y la línea inferior cae en y=306 como en Figma.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-hero.css
  • css/components/side-sheet.css

BS_imageBG_tittle · Header=Tags

bs-hero/hero-tags360 × 308px · stage #ffffff
Header=Tags

Hero de 240px + fila de 68px con tags EVA "Tag text" (Default · Light · Enabled, sin seleccionar) en línea con scroll horizontal; el último tag queda cortado por el borde para indicar que hay más.

Notas de implementación
Tags → EVA eva-3-tag con tag-text y el ícono cruz (tag-icon eva-3-icon-cross, visible solo con -active), igual que la prop cruz=true de Figma. Ajustes mínimos (los mismos que en el header con tags de bs-header): alto 32px (EVA 36px), margin 0 (el espaciado lo da el gap de 8px de Figma) y padding horizontal 15px, porque EVA suma 1px de borde al padding de 16px y en Figma el stroke es interior (tag de 87px). EVA prevalece en el borde Neutral-400 y la tipografía del label (diferencia de antialiasing). Estado seleccionado: agregar -active al .eva-3-tag. Cada tag es role="button" con tabindex para que sea operable con teclado.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-hero.css
  • css/components/side-sheet.css

Bottom Sheet · Sheets completas (mobile 360)

Bottom sheets armadas con las piezas base: header (BS_header o BS_imageBG_tittle), Content con scroll interno y footer (Action Container). El contenedor .sofia-bs solo agrega el fondo, el radio superior, el recorte y el scroll interno del body (.sofia-bs__body); cada pieza conserva sus propias clases y estilos. Sin modificador de altura, la sheet se ajusta a su contenido (frames de la sección "Bottom sheet estructura base (CONTENIDO)"). Con --h-default, --h-75, --h-50 o --h-25 queda anclada al borde inferior del contenedor, sobre .sofia-overlay (component set "bottom sheet" · Altura, sección "Bottom sheet completa"). Animación de apertura y cierre: js/motion/bottom-sheet.js (openBottomSheet(sheetEl, overlayEl) / closeBottomSheet).

Sección Figma 4048:4552 · CSS: css/components/bs-sheets.css · 10 variantes · grupo bs-sheets

Bottom Sheet · Header Title + Dual CTA

bs-sheets/sheet-title-dual360 × 520px · stage #d8d8d8
Header=TitleFooter=Dual CTAAltura=Según contenido

Header con título y cerrar, Content (396px) con el placeholder BS / Slot y footer con "Secondary button" + "Primary button". Usalo cuando el sheet termina con una elección entre dos caminos, por ejemplo cancelar o confirmar. Alto total 520px.

Notas de implementación
Contenedor .sofia-bs: fondo BS/bg/sheet, radio superior 24px (radius large; el frame de Figma tiene rounded-tl/tr 24 y overflow-clip, que recorta el radio 20 del header y la sombra del footer). Body = .sofia-bs-content + .sofia-bs__body: padding 20 en los 4 lados y contenido arriba (la pieza suelta Content usa 16/20 y centra), overflow-y:auto para el scroll interno; el BS / Slot queda en 326px centrado como en Figma. Footer Dual CTA: en esta instancia (4067:959) el Figma no tiene stroke superior y usa la sombra sticky (0 −4 8 · rgba(84,89,98,.1)) en vez de static-hover; además la fila AC_row de 328px queda centrada a 16px de cada borde. Se ajusta solo dentro de .sofia-bs (.sofia-bs > .sofia-bs-footer--dual). Sin gap entre secciones. EVA prevalece: el label de los botones queda ~1px más arriba (line-height EVA) y hay diferencias de antialiasing de texto.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Bottom Sheet · Header Slot + Single CTA

bs-sheets/sheet-slot-single360 × 568px · stage #d8d8d8
Header=SlotFooter=Single CTAAltura=Según contenido

Header con título, cerrar y una fila libre "Content / Slot", Content (396px) con el placeholder "[Put content here]" y footer con un único "Primary button". Usalo cuando el header necesita contenido propio (buscador, resumen, marca). Alto total 568px.

Notas de implementación
Contenedor .sofia-bs: fondo BS/bg/sheet, radio superior 24px (radius large; el frame de Figma tiene rounded-tl/tr 24 y overflow-clip, que recorta el radio 20 del header y la sombra del footer). Body = .sofia-bs-content + .sofia-bs__body: padding 20 en los 4 lados y contenido arriba (la pieza suelta Content usa 16/20 y centra), overflow-y:auto para el scroll interno; el BS / Slot queda en 326px centrado como en Figma. En Figma este frame tiene gap 8px entre header, content y footer: se aplica automáticamente cuando el header es --slot o --tabs (.sofia-bs > .sofia-bs-header--slot ~ *). El Content trae el placeholder gris "[Put content here]" (BS/bg/section #DDE1E8, .sofia-bs__placeholder) en lugar de BS / Slot: reemplazarlo por el contenido. Footer Single CTA tal cual la pieza (stroke superior + static-hover). EVA prevalece: label del botón ~1px más arriba.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Bottom Sheet · Header Tabs + Single CTA

bs-sheets/sheet-tabs-single360 × 594px · stage #d8d8d8
Header=TabsFooter=Single CTAAltura=Según contenido

Header con título, cerrar y 2 tabs EVA ("Titulo" / "Bajada"), Content (396px) con BS / Slot y footer con "Primary button". El contenido de la tab activa va en el body. Alto total 594px.

Notas de implementación
Contenedor .sofia-bs: fondo BS/bg/sheet, radio superior 24px (radius large; el frame de Figma tiene rounded-tl/tr 24 y overflow-clip, que recorta el radio 20 del header y la sombra del footer). Body = .sofia-bs-content + .sofia-bs__body: padding 20 en los 4 lados y contenido arriba (la pieza suelta Content usa 16/20 y centra), overflow-y:auto para el scroll interno; el BS / Slot queda en 326px centrado como en Figma. Gap 8px entre header, content y footer como en Figma (automático con header --tabs). Tabs = pieza bs-header (EVA .eva-3-tabs con los ajustes documentados ahí); el indicador activo es el de EVA (80% del tab). El cambio de tab (-active / aria-selected) y el contenido de cada tab los maneja la app dentro del body. EVA prevalece: label del botón ~1px más arriba.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Bottom Sheet · Header imagen Title + Single CTA

bs-sheets/sheet-hero-title360 × 712px · stage #d8d8d8
Header=Imagen · TitleFooter=Single CTAAltura=Según contenido

Hero con foto, "Titulo" + "Bajada", handle y cerrar circular (BS_imageBG_tittle · Header=Title), Content de 404px con BS / Slot y footer con "Primary button". Para sheets de producto o paquete que arrancan con una imagen. Alto total 712px.

Notas de implementación
Contenedor .sofia-bs: fondo BS/bg/sheet, radio superior 24px (radius large; el frame de Figma tiene rounded-tl/tr 24 y overflow-clip, que recorta el radio 20 del header y la sombra del footer). Body = .sofia-bs-content + .sofia-bs__body: padding 20 en los 4 lados y contenido arriba (la pieza suelta Content usa 16/20 y centra), overflow-y:auto para el scroll interno; el BS / Slot queda en 326px centrado como en Figma. Content de 404px (en Figma este frame usa 404 en lugar de 396): se fija en línea con --sofia-bs-content-height: 404px, la variable que expone la pieza Content. En Figma el frame repite un "Close Button Container" (4055:3894) suelto en x325/y15, exactamente encima del cerrar propio del hero: no se duplica en el HTML (un solo botón Cerrar, el de .sofia-bs-hero). Diferencias del blur/degradado del hero heredadas de bs-hero (ver esa pieza). EVA prevalece: label del botón ~1px más arriba.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-hero.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Bottom Sheet · Header imagen Tags + Single CTA

bs-sheets/sheet-hero-tags360 × 772px · stage #d8d8d8
Header=Imagen · TagsFooter=Single CTAAltura=Según contenido

Hero con foto y fila de tags EVA "Tag text" con scroll horizontal (BS_imageBG_tittle · Header=Tags), Content (396px) con BS / Slot y footer con "Primary button". Alto total 772px.

Notas de implementación
Contenedor .sofia-bs: fondo BS/bg/sheet, radio superior 24px (radius large; el frame de Figma tiene rounded-tl/tr 24 y overflow-clip, que recorta el radio 20 del header y la sombra del footer). Body = .sofia-bs-content + .sofia-bs__body: padding 20 en los 4 lados y contenido arriba (la pieza suelta Content usa 16/20 y centra), overflow-y:auto para el scroll interno; el BS / Slot queda en 326px centrado como en Figma. Sin gap entre secciones (como en Figma). Tags = pieza bs-hero (EVA .eva-3-tag con los ajustes mínimos documentados ahí: 32px de alto y 87px de ancho como en Figma), así que el corte del último tag cae en el mismo lugar. Diferencias del blur del hero heredadas de bs-hero.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-hero.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Bottom Sheet · Header imagen Tabs + Single CTA

bs-sheets/sheet-hero-tabs360 × 772px · stage #d8d8d8
Header=Imagen · TabsFooter=Single CTAAltura=Según contenido

Hero con foto y fila de 2 tabs EVA (BS_imageBG_tittle · Header=Tabs), Content (396px) con BS / Slot y footer con "Primary button". Alto total 772px.

Notas de implementación
Contenedor .sofia-bs: fondo BS/bg/sheet, radio superior 24px (radius large; el frame de Figma tiene rounded-tl/tr 24 y overflow-clip, que recorta el radio 20 del header y la sombra del footer). Body = .sofia-bs-content + .sofia-bs__body: padding 20 en los 4 lados y contenido arriba (la pieza suelta Content usa 16/20 y centra), overflow-y:auto para el scroll interno; el BS / Slot queda en 326px centrado como en Figma. Sin gap entre secciones (como en Figma). Tabs = pieza bs-hero (EVA .eva-3-tabs con los ajustes mínimos documentados ahí: ítem de 64px y texto alineado a la izquierda como en Figma); el indicador activo es el de EVA (80% del tab). Diferencias del blur del hero heredadas de bs-hero.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-hero.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

bottom sheet · Altura=Default

bs-sheets/height-default360 × 737px · stage #ffffff
Header=TitleFooter=Single CTAAltura=Default

Sheet completa sobre el overlay, anclada abajo, con el alto de su contenido: header Title + Content de 396px + footer Single CTA (520px). Es la altura por defecto.

Notas de implementación
Snippet = .sofia-overlay + .sofia-bs.sofia-bs--h-default, ambos absolutos dentro del contenedor posicionado (en la preview, el viewport de 360×737; en producción el shell del chat o position:fixed). Radio superior = token BS/radius/sheet (--sofia-bs-radius-sheet, 16px), que es el que tiene atado el componente; los frames de estructura base usan 24px. Overlay opaco #323439 como en Figma (pieza bs-header). La sheet no tiene sombra propia en Figma: solo la del footer (static-hover, cae fuera del viewport). Diferencia de posición: en Figma todas las alturas terminan en y=739, 2px por debajo del frame de 737 (desbordan); acá la sheet se ancla a bottom:0, así que el footer queda 2px más arriba que en el render de Figma. Default se ajusta al contenido: header Title 56 + Content 396 (padding 20) + footer 68 = 520px, y con max-height:100% el body se achica y scrollea en pantallas bajas. Con el desplazamiento de 2px compensado, el render coincide con Figma salvo antialiasing de texto. EVA prevalece: label del botón ~1px más arriba.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

bottom sheet · Altura=75%

bs-sheets/height-75360 × 737px · stage #ffffff
Header=TitleFooter=Single CTAAltura=75%

Sheet anclada abajo que ocupa el 75% del alto del contenedor. El body toma el espacio libre entre header y footer y scrollea si el contenido no entra.

Notas de implementación
Snippet = .sofia-overlay + .sofia-bs.sofia-bs--h-75, ambos absolutos dentro del contenedor posicionado (en la preview, el viewport de 360×737; en producción el shell del chat o position:fixed). Radio superior = token BS/radius/sheet (--sofia-bs-radius-sheet, 16px), que es el que tiene atado el componente; los frames de estructura base usan 24px. Overlay opaco #323439 como en Figma (pieza bs-header). La sheet no tiene sombra propia en Figma: solo la del footer (static-hover, cae fuera del viewport). Diferencia de posición: en Figma todas las alturas terminan en y=739, 2px por debajo del frame de 737 (desbordan); acá la sheet se ancla a bottom:0, así que el footer queda 2px más arriba que en el render de Figma. Alto = 75% del contenedor (height:75%); el body (padding 10/8 como en Figma) ocupa el espacio entre header y footer con overflow-y:auto. En Figma la sheet mide 557px sobre 737 (Content 433px), unos 4px más que el 75% exacto: por eso el header queda ~2px más abajo y el footer 2px más arriba que en el render de Figma; se priorizó el porcentaje para que el snap point funcione en cualquier alto de pantalla. El BS / Slot mantiene 326px centrado (con padding 8 no se estira). EVA prevalece: label del botón ~1px más arriba.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

bottom sheet · Altura=50%

bs-sheets/height-50360 × 737px · stage #ffffff
Header=TitleFooter=Single CTAAltura=50%

Sheet anclada abajo que ocupa el 50% del alto del contenedor. El body toma el espacio libre entre header y footer y scrollea si el contenido no entra.

Notas de implementación
Snippet = .sofia-overlay + .sofia-bs.sofia-bs--h-50, ambos absolutos dentro del contenedor posicionado (en la preview, el viewport de 360×737; en producción el shell del chat o position:fixed). Radio superior = token BS/radius/sheet (--sofia-bs-radius-sheet, 16px), que es el que tiene atado el componente; los frames de estructura base usan 24px. Overlay opaco #323439 como en Figma (pieza bs-header). La sheet no tiene sombra propia en Figma: solo la del footer (static-hover, cae fuera del viewport). Diferencia de posición: en Figma todas las alturas terminan en y=739, 2px por debajo del frame de 737 (desbordan); acá la sheet se ancla a bottom:0, así que el footer queda 2px más arriba que en el render de Figma. Alto = 50% del contenedor (height:50%); el body (padding 10/8 como en Figma) ocupa el espacio entre header y footer con overflow-y:auto. En Figma la sheet mide 373px sobre 737 (Content 249px), unos 4px más que el 50% exacto: por eso el header queda ~2px más abajo y el footer 2px más arriba que en el render de Figma; se priorizó el porcentaje para que el snap point funcione en cualquier alto de pantalla. El BS / Slot mantiene 326px centrado (con padding 8 no se estira). EVA prevalece: label del botón ~1px más arriba.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

bottom sheet · Altura=25%

bs-sheets/height-25360 × 737px · stage #ffffff
Header=TitleFooter=Single CTAAltura=25%

Sheet anclada abajo que ocupa el 25% del alto del contenedor, para contenido muy breve (una línea o un dato). El body contiene el placeholder "[Put content here]" de 24px.

Notas de implementación
Snippet = .sofia-overlay + .sofia-bs.sofia-bs--h-25, ambos absolutos dentro del contenedor posicionado (en la preview, el viewport de 360×737; en producción el shell del chat o position:fixed). Radio superior = token BS/radius/sheet (--sofia-bs-radius-sheet, 16px), que es el que tiene atado el componente; los frames de estructura base usan 24px. Overlay opaco #323439 como en Figma (pieza bs-header). La sheet no tiene sombra propia en Figma: solo la del footer (static-hover, cae fuera del viewport). Diferencia de posición: en Figma todas las alturas terminan en y=739, 2px por debajo del frame de 737 (desbordan); acá la sheet se ancla a bottom:0, así que el footer queda 2px más arriba que en el render de Figma. Alto = 25% del contenedor (height:25%); el body (padding 10/8 como en Figma) ocupa el espacio entre header y footer con overflow-y:auto. En Figma la sheet mide 188px sobre 737 (Content 64px), unos 4px más que el 25% exacto: por eso el header queda ~2px más abajo y el footer 2px más arriba que en el render de Figma; se priorizó el porcentaje para que el snap point funcione en cualquier alto de pantalla. El body trae el placeholder "[Put content here]" de 320×24 (BS/bg/section) arriba, no estirado (.sofia-bs--h-25 .sofia-bs__placeholder). EVA prevalece: label del botón ~1px más arriba.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Bottom Sheet · Ejemplos y tamaños dentro del chat

Pantallas CHAT de SOFIA (mobile 360) con el bottom sheet abierto a distintas alturas dentro de la conversación: sin sheet, 25%, 50%, 75% y 100% (modal full screen). El shell del chat (.sofia-chat) arma el header (status bar + app bar), la conversación (userDialogue) y el input; la sheet se apoya en .sofia-chat__sheet (capa con .sofia-overlay) y .sofia-chat__panel, y reutiliza tal cual las piezas .sofia-bs-header, .sofia-bs-content y .sofia-bs-footer. Las alturas están medidas en el área del chat que queda debajo del header (673px en un CHAT de 753px).

Sección Figma 4055:3912 · CSS: css/components/bs-chat.css · 5 variantes · grupo bs-chat

CHAT · sin bottom sheet

bs-chat/chat-empty360 × 753px · stage #ffffff

Pantalla de referencia del chat sin sheet: header (status bar "10:00" + volver y carrito), el mensaje del usuario "Quiero ver vuelos a Madrid" y el input ("Escribe tu consulta..." + TyC). En Figma, a la izquierda de este frame hay una regla de alturas que no forma parte del snippet: una barra vertical (Rectangle 1502, 10×673px, fill #130BFD) que va del borde inferior del header (y=80) al final del chat (y=753), con líneas guía horizontales hasta el borde derecho del CHAT y textos Roboto Mono Medium 16/20: 100% en y=80 (673px de alto), 75% en y=248 (505px), 50% en y≈416.5 (336.5px) y 25% en y=575 (178px). Las posiciones se miden desde el borde superior del CHAT y los altos desde el borde inferior.

Notas de implementación
Shell .sofia-chat: columna flex que ocupa todo el contenedor (width/height 100%), con header (80px) e input (108px) fijos y .sofia-chat__body scrolleable en el medio. El primer turno arranca a 24px del header (--sofia-bs-turn-gap; userDialogue en y=104). Se revisaron las piezas de sofia-components-library (header/index.html, sofia-input/index.html, user-dialog/index.html) y no coinciden con estas instancias: el status bar tiene padding 4/8 e íconos dibujados a mano, el input usa padding 6/12 y TyC 8/16/4, y el user-dialog usa una flecha poligonal propia y un avatar de 30px. Por eso se implementaron desde get_design_context de este archivo, con scope .sofia-chat. Header: .statusBar de 24px con padding 4/16 y hora en Google Sans Flex Regular 12/14 rgba(0,0,0,.54) (como en Figma); los 3 íconos de sistema son assets de Figma (assets/icons/chat-status-*.svg, 14×14) porque EVA no los tiene. App bar con padding 16: volver = eva-3-icon-arrow-bold-left y carrito = eva-3-icon-trolley (Icon container Md, 24px, Neutral-800). Burbuja: Neutral-100, padding 16, radio 16, texto Body M (Rubik 14/20) alineado a la derecha, con la cola de Figma (assets/img/chat-dialog-tail.svg) rotada 90°. Avatar: EVA eva-3-avatar -image -md con ajuste mínimo a 28px (EVA -md mide 30px) y la foto de Figma (assets/img/chat-user-avatar.png). Input: menú 52×52 (borde Neutral-400, radio 24, eva-3-icon-list a 20px), un pill de 52px con <input> y botón de micrófono EVA eva-3-btn -md -link -circle (36×36, eva-3-icon-microphone a 16px). Ajuste mínimo: el ícono va en Neutral-800 como en Figma (EVA -link lo pinta Brand/Primary-500). La sombra sticky (0 −4 8) va en la fila del input, y TyC Rubik 10/12 tiene el link "términos y condiciones." en Medium #4300D2. Figma devuelve como asset del micrófono el placeholder genérico del Icon container; se usa el glifo EVA microphone, que es el que se ve en el render. Los tokens --sofia-bs-header-height (101px) y --sofia-bs-input-area-height (124px) del MD no coinciden con estos frames (80 y 108px) y no se usan. Render: los textos Body M (Rubik 14/20: burbuja y placeholder del input) quedan ~1px más arriba que en Figma con la caja de texto en la misma posición (redondeo del half-leading de Rubik en Chrome); no se compensa con padding.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css

CHAT · bottom sheet 25%

bs-chat/chat-bs-25360 × 753px · stage #ffffff
Altura=25%

Sheet al 25%: el overlay oscurece la conversación y la sheet asoma por encima del input. Se ven el handle, el título y el comienzo del contenido (86px visibles), y el input sigue visible y usable por encima del overlay. Usalo para anticipar contenido sin tapar el input: el usuario arrastra el handle para expandir la sheet.

Notas de implementación
Sheet .sofia-chat__sheet--25 con z-index 1, por debajo del input (z-index 2), igual que el orden de capas de Figma, donde inputSofia está por encima del frame "bottom sheet". Panel de 160px fijos a 34px del borde inferior (y 559→719): por eso solo quedan visibles por encima del input (y=645) el header y 20px del contenido. Header = .sofia-bs-header--title (con handle). Content = .sofia-bs-content de 179px con padding 10/8 y placeholder [Put content here] gris (BS/bg/section, 344px de ancho), recortado por el panel. El Action Container de este frame está vacío en Figma y queda fuera de los 160px, por eso no se incluye. Panel con radio superior 24 y overflow hidden. En Figma la capa "bottom sheet" arranca en y=33, detrás del header del chat: acá cubre todo el chat (inset 0) y el header queda por encima, y visualmente es lo mismo.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-header.css
  • css/components/side-sheet.css

CHAT · bottom sheet 50%

bs-chat/chat-bs-50360 × 753px · stage #ffffff
Altura=50%

Sheet a media altura sobre el chat: header con título + cerrar, contenido de 224px y footer con un CTA primario. Tapa el input; el header del chat queda visible arriba del overlay. Es el tamaño por defecto para tareas cortas: elegir una opción o confirmar.

Notas de implementación
Sheet .sofia-chat__sheet--50 por encima del input. Panel de 348px de alto por contenido: 56 de .sofia-bs-header--title, 224 de .sofia-bs-content (--sofia-bs-content-height: 224px, padding 20 y placeholder gris de 320×184) y 68 de .sofia-bs-footer--single ("Primary button", EVA eva-3-btn -md -primary). En Figma la sheet arranca en y=407.5 y sobresale 2.5px por debajo del CHAT (recortada): se replica con --sofia-chat-sheet-bottom: -2.5px. En producción se puede anclar en 0. La capa del overlay en Figma arranca en y=80 (alto 673).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/side-sheet.css

CHAT · bottom sheet 75%

bs-chat/chat-bs-75360 × 753px · stage #ffffff
Altura=75%

Sheet a 3/4 del chat: header con título + cerrar, contenido de 432px y footer con un CTA primario. Deja visible el header del chat y una franja del overlay. Usalo cuando el contenido necesita más espacio (listas, formularios) pero conviene mantener el contexto del chat detrás.

Notas de implementación
Sheet .sofia-chat__sheet--75 por encima del input. Panel de 556px de alto por contenido: 56 de header, 432 de content (--sofia-bs-content-height: 432px, padding 20, placeholder de 320×392) y 68 de footer Single CTA. En Figma la sheet arranca en y=198 y sobresale 1px por debajo del CHAT: se replica con --sofia-chat-sheet-bottom: -1px. La capa "bottom sheet" de Figma arranca en y=33, detrás del header del chat.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/side-sheet.css

CHAT · bottom sheet 100% (modal full screen)

bs-chat/chat-bs-100360 × 746px · stage #ffffff
Altura=100%

Modal full screen: si el contenido a utilizar es muy extenso transformamos la bottom en un modal full screen para que el usuario pueda navegar. La sheet ocupa todo el chat, sin overlay ni handle: header con título + cerrar (44px), contenido de 606px y footer con el CTA primario centrado en los 96px restantes.

Pautas en Figma
  • 1. Modal full screen — Si el contenido a utilizar es muy extenso transformamos la bottom en un modal full screen para que el usuario pueda navegar.
Notas de implementación
Texto de la card 4056:4604 de Figma (nota "1. Modal full screen"), copiado literal en guidelines. Sheet .sofia-chat__sheet--100 con z-index 5, por encima de todo el chat (en este frame de Figma no hay header del chat, la sheet arranca en y=0). Panel a todo el alto (746px) con radio superior 24. Header = .sofia-bs-header--title SIN el bloque .sofia-bs-header__handle (en Figma la instancia no tiene handle), así queda en 44px (12 + 20 + 12). Content = .sofia-bs-content de 606px con padding 20 y placeholder gris de 320×566. Footer = .sofia-bs-footer--single con flex 1 (en Figma el Action Container es flex-1 y mide 96px), con el CTA centrado verticalmente. No lleva overlay. userDialogue e input siguen en el DOM debajo de la sheet, igual que en Figma.
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/side-sheet.css

Motion · 1. Bottom Sheet · Mobile

Texto 4181:27766 · Qué evitar 4181:27770 · Código 4123:2470

¿Qué es?

Una capa anclada al borde inferior de la pantalla. Se usa en mobile cuando el contenido es complementario a lo que el usuario ya está viendo: opciones, filtros, detalles de un ítem, confirmaciones.

¿Qué queremos que comunique la animación?

Entrada: el sheet sube desde abajo como si estuviera oculto fuera del viewport. Esto refuerza la metáfora de que el contenido existía debajo de la pantalla y el usuario lo trajo hacia arriba. No es magia —es un cajón que se abre.

Salida: el sheet baja de nuevo al lugar del que vino. El usuario entiende intuitivamente que el contenido "volvió", no que fue destruido.

Qué evitar
  • No usar scale en el sheet: rompería la metáfora de "cajón físico".
  • No animar el alto del sheet durante la entrada: debe aparecer ya con su tamaño final.
  • No usar ease-in al inicio: el arranque lento en mobile se siente laggy.
1. Bottom Sheet · Mobile — referencia de Figma
image 1 · 4181:27768
Decisiones de diseño
VariableValorPor qué
Duración320 msLo suficientemente rápido para no sentirse lento en mobile, lo suficientemente lento para que el ojo siga el movimiento.
Easingcubic-bezier(0.32, 0.72, 0, 1)Curva con arranque rápido y desaceleración pronunciada al final. Simula inercia física: algo que sube con impulso y frena suavemente.
translateY inicio100%El sheet parte exactamente fuera del viewport, desde el borde inferior.
Opacidad inicio0.6No arranca en cero para evitar una entrada "espectacular". El movimiento ya comunica suficiente; el fade es sutil.
OverlayFade simple 0 → 1El overlay no necesita moverse, solo reforzar el foco visual.
Comportamiento esperado
[VIEWPORT]
─────────────────────
 contenido principal       ← queda visible debajo del overlay
─────────────────────
 ██████████████████████    ← sheet sube desde abajo
 ██████ bottom sheet ████
 ██████████████████████
Código literal del handoff · js/motion/bottom-sheet.js
js/motion/bottom-sheet.js
// ─── Bottom Sheet · Mobile ───────────────────────────────────────────────────
// Desliza desde el borde inferior. Incluye overlay con fade.
// Uso:
//   openBottomSheet(sheetEl, overlayEl)
//   closeBottomSheet(sheetEl, overlayEl)

const BOTTOM_SHEET = {
  duration: 320,
  easing: "cubic-bezier(0.32, 0.72, 0, 1)", // spring suave
};

export function openBottomSheet(sheet, overlay) {
  overlay.style.display = "block";

  overlay.animate(
    [{ opacity: 0 }, { opacity: 1 }],
    { duration: BOTTOM_SHEET.duration, easing: "ease", fill: "forwards" }
  );

  sheet.style.display = "flex";
  sheet.animate(
    [
      { transform: "translateY(100%)", opacity: 0.6 },
      { transform: "translateY(0)",    opacity: 1   },
    ],
    { duration: BOTTOM_SHEET.duration, easing: BOTTOM_SHEET.easing, fill: "forwards" }
  );
}

export function closeBottomSheet(sheet, overlay) {
  const anim = sheet.animate(
    [
      { transform: "translateY(0)",    opacity: 1   },
      { transform: "translateY(100%)", opacity: 0.6 },
    ],
    { duration: BOTTOM_SHEET.duration, easing: BOTTOM_SHEET.easing, fill: "forwards" }
  );

  overlay.animate(
    [{ opacity: 1 }, { opacity: 0 }],
    { duration: BOTTOM_SHEET.duration, easing: "ease", fill: "forwards" }
  );

  anim.onfinish = () => {
    sheet.style.display   = "none";
    overlay.style.display = "none";
  };
}

Side Sheet

Definición (Figma · “sidesheet”)

Es una superficie que emerge desde el borde inferior de la pantalla y se apoya sobre el contenido principal, sin reemplazarlo. Convive con la vista de origen en lugar de navegar a una nueva ruta: el contexto queda visible (o al menos implícito) detrás de un scrim, y la superficie se puede descartar deslizando hacia abajo, tocando el fondo o cerrando con la X.

Solo funciona para desktop.

Cuándo / para qué
  • cuando usamos sidesheet → es información que complementa la card por ejemplo

Plataforma: Desktop

Playground · Side Sheet

Abrir ↗

Side Sheet · Desktop

Definición (Figma): "cuando usamos sidesheet → es información que complementa la card por ejemplo". Es una capa de 421px anclada al borde derecho, sobre un overlay, que ocupa todo el alto del viewport desktop. Sirve para contenido que complementa la vista principal sin reemplazarla: paneles de detalle, filtros avanzados, configuraciones. Se arma con las mismas piezas del bottom sheet: header propio (Side_header, título Title M + cerrar) o hero con imagen (BS_imageBG_tittle), navegación opcional con tabs o tags, Content con BS / Slot y Action Container Single CTA. Animación: js/motion/side-sheet.js (openSideSheet / closeSideSheet: entra deslizando desde la derecha, translateX 100% → 0 + opacidad 0.8 → 1, 340 ms, cubic-bezier(0.32, 0.72, 0, 1); el overlay hace fade). No usar scale ni animar el ancho del panel.

Sección Figma 4119:1787 · CSS: css/components/side-sheet.css · 7 variantes · grupo side-sheet

Side_sheet · Title

side-sheet/ss-title421 × 770px · stage #ffffff
Header=Side_header (Title M)Navegación=—Footer=Single CTA

Side sheet básico: Side_header con "Title" (Title M, 24/30) y cerrar, Content con el placeholder BS / Slot y Action Container con "Primary button". Es la versión por defecto para detalle o información complementaria.

Notas de implementación
Side_header (4121:2084): .sofia-ss__header.sofia-ss-header, padding --sofia-ss-spacing-header-v (10) / --sofia-bs-spacing-header-h (20), alto 50px. Título <h2 class="eva-3-h3"> = Title M (Rubik Medium 24/30, Neutral-800). Cerrar = <button> 16×16 con el ícono EVA eva-3-icon-close a 24px desbordando (equivalente al 'cross' de Figma, igual que bs-header Handle=Title). El handle (Frame 4) está oculto en Figma y no se incluye. Estructura: .sofia-ss (columna de 421px, --sofia-ss-width, a todo el alto del contenedor). El Content (.sofia-bs-content + .sofia-ss__body) es flex:1 y absorbe el alto libre: en Figma cada instancia tiene un alto fijo distinto (652/601/620/468/413px), que es exactamente lo que queda entre header y footer, por eso la preview coincide al alto del nodo. BS / Slot a 326px fijos centrado (en Figma también mide 326 a 421px; se sobreescribe el calc(100% + 6px) de bs-header solo dentro de .sofia-ss). Footer = Action Container Single CTA (.sofia-bs-footer--single, sin cambios): fila de 328px centrada a 46.5px, padding vertical --sofia-ss-spacing-action-v (16px), stroke superior Borders/medium y sombra static-hover (cae fuera del sheet). EVA: no se usa .eva-3-modal-side-sheet porque difiere del Figma (-sm 424px, header 24/24/20 con título 20/26, contenido 0 24 24, footer 20 24 24, overlay rgba(0,0,0,.8) y visibilidad por clases); los átomos sí son EVA. EVA prevalece en el CTA: el label queda ~1px más arriba (line-height EVA) y el borde izquierdo/derecho cae en medio píxel (46.5px).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Side_sheet · Title + Tabs

side-sheet/ss-tabs421 × 771px · stage #ffffff
Header=Side_header (Title M)Navegación=.Tab Container Desktop · 6 tabsFooter=Single CTA

Side_header + fila de tabs EVA ("Tab label", la primera seleccionada) + Content + Action Container. Usalo cuando la información complementaria se divide en secciones del mismo nivel.

Notas de implementación
Side_header (4121:2084): .sofia-ss__header.sofia-ss-header, padding --sofia-ss-spacing-header-v (10) / --sofia-bs-spacing-header-h (20), alto 50px. Título <h2 class="eva-3-h3"> = Title M (Rubik Medium 24/30, Neutral-800). Cerrar = <button> 16×16 con el ícono EVA eva-3-icon-close a 24px desbordando (equivalente al 'cross' de Figma, igual que bs-header Handle=Title). El handle (Frame 4) está oculto en Figma y no se incluye. Tabs = EVA .eva-3-tabs.-no-stroke con 6 .tabs-nav-item "Tab label" (el primero -active, role=tablist/tab + aria-selected); el panel .tabs-container se omite porque .Swap-Content está oculto y el contenido vive en el Content (role=tabpanel). Ajuste mínimo (igual que el modal): ítem de 51px + padding-top 4px para que la fila mida 52px como el .Tab Container. EVA prevalece: dibuja la línea inferior Neutral-300 de 1px bajo toda la fila (en el render de Figma no se ve) y el indicador activo queda 1px más arriba, sobre esa línea; el indicador EVA mide 80% del tab (Figma ~86%); los divisores son pseudo-elementos (sin 1px propio). El cambio de tab (-active / aria-selected) y el desplazamiento horizontal (EVA mueve .tabs-nav con las flechas tabs-nav-arrow-first/last, ocultas en Figma) los maneja la app. Side_sheet · Tabs (4121:2176): en Figma el .Subnav conserva el ancho desktop de 1024px (6 tabs de ~170px) y el side sheet lo recorta, así que se ven 2 tabs y media; se replica con width: var(--sofia-ss-subnav-width, 1024px) en .tabs-nav (modificador .sofia-ss--tabs). Estructura: .sofia-ss (columna de 421px, --sofia-ss-width, a todo el alto del contenedor). El Content (.sofia-bs-content + .sofia-ss__body) es flex:1 y absorbe el alto libre: en Figma cada instancia tiene un alto fijo distinto (652/601/620/468/413px), que es exactamente lo que queda entre header y footer, por eso la preview coincide al alto del nodo. BS / Slot a 326px fijos centrado (en Figma también mide 326 a 421px; se sobreescribe el calc(100% + 6px) de bs-header solo dentro de .sofia-ss). Footer = Action Container Single CTA (.sofia-bs-footer--single, sin cambios): fila de 328px centrada a 46.5px, padding vertical --sofia-ss-spacing-action-v (16px), stroke superior Borders/medium y sombra static-hover (cae fuera del sheet). EVA: no se usa .eva-3-modal-side-sheet porque difiere del Figma (-sm 424px, header 24/24/20 con título 20/26, contenido 0 24 24, footer 20 24 24, overlay rgba(0,0,0,.8) y visibilidad por clases); los átomos sí son EVA. EVA prevalece en el CTA: el label queda ~1px más arriba (line-height EVA) y el borde izquierdo/derecho cae en medio píxel (46.5px).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Side_sheet · Title + Tags

side-sheet/ss-tags421 × 790px · stage #ffffff
Header=Side_header (Title M)Navegación=4 Tag (Default · Light · no seleccionado)Footer=Single CTA

Side_header + fila de 4 tags EVA "Tag text" (filtros rápidos) + Content + Action Container. Usalo cuando el contenido del panel se puede filtrar.

Notas de implementación
Side_header (4121:2084): .sofia-ss__header.sofia-ss-header, padding --sofia-ss-spacing-header-v (10) / --sofia-bs-spacing-header-h (20), alto 50px. Título <h2 class="eva-3-h3"> = Title M (Rubik Medium 24/30, Neutral-800). Cerrar = <button> 16×16 con el ícono EVA eva-3-icon-close a 24px desbordando (equivalente al 'cross' de Figma, igual que bs-header Handle=Title). El handle (Frame 4) está oculto en Figma y no se incluye. Fila de tags (.sofia-ss__tags): padding 10/20, gap 8px, alto 52px, scroll horizontal sin scrollbar. Tags = EVA .eva-3-tag (Default · Light · Enabled, no seleccionado) con tag-text + tag-icon eva-3-icon-cross (Figma cruz=true; EVA lo muestra solo con -active). Ajuste mínimo: alto 32px (EVA 36px) y margin 0 (el espaciado lo da el gap). EVA prevalece: el borde de 1px se suma por fuera del padding de 16px → cada tag mide 89px en vez de 87px (la fila se corre 2px por tag) y el texto queda ~1px más arriba. Estado seleccionado: agregar -active. Los 4 tags entran en 421px (el último termina a ~20px del borde). Estructura: .sofia-ss (columna de 421px, --sofia-ss-width, a todo el alto del contenedor). El Content (.sofia-bs-content + .sofia-ss__body) es flex:1 y absorbe el alto libre: en Figma cada instancia tiene un alto fijo distinto (652/601/620/468/413px), que es exactamente lo que queda entre header y footer, por eso la preview coincide al alto del nodo. BS / Slot a 326px fijos centrado (en Figma también mide 326 a 421px; se sobreescribe el calc(100% + 6px) de bs-header solo dentro de .sofia-ss). Footer = Action Container Single CTA (.sofia-bs-footer--single, sin cambios): fila de 328px centrada a 46.5px, padding vertical --sofia-ss-spacing-action-v (16px), stroke superior Borders/medium y sombra static-hover (cae fuera del sheet). EVA: no se usa .eva-3-modal-side-sheet porque difiere del Figma (-sm 424px, header 24/24/20 con título 20/26, contenido 0 24 24, footer 20 24 24, overlay rgba(0,0,0,.8) y visibilidad por clases); los átomos sí son EVA. EVA prevalece en el CTA: el label queda ~1px más arriba (line-height EVA) y el borde izquierdo/derecho cae en medio píxel (46.5px).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Side_sheet · Hero + Title

side-sheet/ss-hero-title421 × 776px · stage #ffffff
Header=BS_imageBG_tittle · Header=TitleNavegación=—Footer=Single CTA

Side sheet que arranca con el hero de imagen (240px, "Titulo" + "Bajada" y cerrar circular), Content y Action Container. Usalo para detalle de producto/paquete que lidera con una foto.

Notas de implementación
Hero = BS_imageBG_tittle · Header=Title (instancia a 421px): mismo markup que bs-hero--title sin el span .sofia-bs-hero__handle (oculto en Figma) y con --sofia-bs-hero-radius: 0 (scopeado en .sofia-ss). Foto bs-hero-house.png (mismo archivo que exporta Figma), Rectangle 40 = bs-hero-scrim-bottom.svg (idéntico salvo ids), centrado a 360px como en Figma. Rectangle 38: a 421px Figma exporta otra forma (453×130) → nuevo asset ss-hero-scrim-text.svg, y la máscara del background blur usa ese mismo path (override scopeado en side-sheet.css). Texto a 320px fijos y cerrar circular de 20px arriba a la derecha (15/15). Diferencias residuales: la textura del blur de Chrome es algo más uniforme que la de Figma (documentado en bs-hero) y "Bajada" (Body M 14/20) queda ~1px más arriba con la caja en la misma posición (redondeo del half-leading de Rubik en Chrome). Estructura: .sofia-ss (columna de 421px, --sofia-ss-width, a todo el alto del contenedor). El Content (.sofia-bs-content + .sofia-ss__body) es flex:1 y absorbe el alto libre: en Figma cada instancia tiene un alto fijo distinto (652/601/620/468/413px), que es exactamente lo que queda entre header y footer, por eso la preview coincide al alto del nodo. BS / Slot a 326px fijos centrado (en Figma también mide 326 a 421px; se sobreescribe el calc(100% + 6px) de bs-header solo dentro de .sofia-ss). Footer = Action Container Single CTA (.sofia-bs-footer--single, sin cambios): fila de 328px centrada a 46.5px, padding vertical --sofia-ss-spacing-action-v (16px), stroke superior Borders/medium y sombra static-hover (cae fuera del sheet). EVA: no se usa .eva-3-modal-side-sheet porque difiere del Figma (-sm 424px, header 24/24/20 con título 20/26, contenido 0 24 24, footer 20 24 24, overlay rgba(0,0,0,.8) y visibilidad por clases); los átomos sí son EVA. EVA prevalece en el CTA: el label queda ~1px más arriba (line-height EVA) y el borde izquierdo/derecho cae en medio píxel (46.5px).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-hero.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Side_sheet · Hero + Tags

side-sheet/ss-hero-tags421 × 773px · stage #ffffff
Header=BS_imageBG_tittle · Header=TitleNavegación=4 Tag (Frame 5)Footer=Single CTA

Hero de imagen + fila de 4 tags "Tag text" + Content + Action Container.

Notas de implementación
Hero = BS_imageBG_tittle · Header=Title (instancia a 421px): mismo markup que bs-hero--title sin el span .sofia-bs-hero__handle (oculto en Figma) y con --sofia-bs-hero-radius: 0 (scopeado en .sofia-ss). Foto bs-hero-house.png (mismo archivo que exporta Figma), Rectangle 40 = bs-hero-scrim-bottom.svg (idéntico salvo ids), centrado a 360px como en Figma. Rectangle 38: a 421px Figma exporta otra forma (453×130) → nuevo asset ss-hero-scrim-text.svg, y la máscara del background blur usa ese mismo path (override scopeado en side-sheet.css). Texto a 320px fijos y cerrar circular de 20px arriba a la derecha (15/15). Diferencias residuales: la textura del blur de Chrome es algo más uniforme que la de Figma (documentado en bs-hero) y "Bajada" (Body M 14/20) queda ~1px más arriba con la caja en la misma posición (redondeo del half-leading de Rubik en Chrome). Debajo del hero, Frame 5 (4121:2283) = la misma fila de tags que ss-tags (no es la fila de 68px de bs-hero--tags: acá mide 52px con padding 10/20). Fila de tags (.sofia-ss__tags): padding 10/20, gap 8px, alto 52px, scroll horizontal sin scrollbar. Tags = EVA .eva-3-tag (Default · Light · Enabled, no seleccionado) con tag-text + tag-icon eva-3-icon-cross (Figma cruz=true; EVA lo muestra solo con -active). Ajuste mínimo: alto 32px (EVA 36px) y margin 0 (el espaciado lo da el gap). EVA prevalece: el borde de 1px se suma por fuera del padding de 16px → cada tag mide 89px en vez de 87px (la fila se corre 2px por tag) y el texto queda ~1px más arriba. Estado seleccionado: agregar -active. Estructura: .sofia-ss (columna de 421px, --sofia-ss-width, a todo el alto del contenedor). El Content (.sofia-bs-content + .sofia-ss__body) es flex:1 y absorbe el alto libre: en Figma cada instancia tiene un alto fijo distinto (652/601/620/468/413px), que es exactamente lo que queda entre header y footer, por eso la preview coincide al alto del nodo. BS / Slot a 326px fijos centrado (en Figma también mide 326 a 421px; se sobreescribe el calc(100% + 6px) de bs-header solo dentro de .sofia-ss). Footer = Action Container Single CTA (.sofia-bs-footer--single, sin cambios): fila de 328px centrada a 46.5px, padding vertical --sofia-ss-spacing-action-v (16px), stroke superior Borders/medium y sombra static-hover (cae fuera del sheet). EVA: no se usa .eva-3-modal-side-sheet porque difiere del Figma (-sm 424px, header 24/24/20 con título 20/26, contenido 0 24 24, footer 20 24 24, overlay rgba(0,0,0,.8) y visibilidad por clases); los átomos sí son EVA. EVA prevalece en el CTA: el label queda ~1px más arriba (line-height EVA) y el borde izquierdo/derecho cae en medio píxel (46.5px).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-hero.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Side_sheet · Hero + Tabs

side-sheet/ss-hero-tabs421 × 775px · stage #ffffff
Header=BS_imageBG_tittle · Header=TitleNavegación=.Tab Container Desktop · 6 tabs (Frame 6)Footer=Single CTA

Hero de imagen + fila de tabs EVA "Tab label" + Content + Action Container.

Notas de implementación
Hero = BS_imageBG_tittle · Header=Title (instancia a 421px): mismo markup que bs-hero--title sin el span .sofia-bs-hero__handle (oculto en Figma) y con --sofia-bs-hero-radius: 0 (scopeado en .sofia-ss). Foto bs-hero-house.png (mismo archivo que exporta Figma), Rectangle 40 = bs-hero-scrim-bottom.svg (idéntico salvo ids), centrado a 360px como en Figma. Rectangle 38: a 421px Figma exporta otra forma (453×130) → nuevo asset ss-hero-scrim-text.svg, y la máscara del background blur usa ese mismo path (override scopeado en side-sheet.css). Texto a 320px fijos y cerrar circular de 20px arriba a la derecha (15/15). Diferencias residuales: la textura del blur de Chrome es algo más uniforme que la de Figma (documentado en bs-hero) y "Bajada" (Body M 14/20) queda ~1px más arriba con la caja en la misma posición (redondeo del half-leading de Rubik en Chrome). Frame 6 (4121:2332): fila de tabs con padding 10/20 y el .Tab Container a 381px. En Figma el auto-layout del Side_sheet tiene espaciado −6px: cada bloque pisa 6px del anterior (el hero se ve 234px y la fila de tabs 66px); se replica con margin-top: var(--sofia-ss-hero-tabs-overlap, -6px) entre hijos de .sofia-ss--hero-tabs. En ese Subnav de 381px Figma centra 6 tabs de 110px (min-width), que desbordan por ambos lados: el primer tab (el seleccionado) queda fuera de vista a −142px y se ve un tab cortado a cada lado; se replica con justify-content:center en .tabs-nav (EVA queda a −139.5px porque sus divisores no ocupan ancho: ±2px). Tabs = EVA .eva-3-tabs.-no-stroke con 6 .tabs-nav-item "Tab label" (el primero -active, role=tablist/tab + aria-selected); el panel .tabs-container se omite porque .Swap-Content está oculto y el contenido vive en el Content (role=tabpanel). Ajuste mínimo (igual que el modal): ítem de 51px + padding-top 4px para que la fila mida 52px como el .Tab Container. EVA prevalece: dibuja la línea inferior Neutral-300 de 1px bajo toda la fila (en el render de Figma no se ve) y el indicador activo queda 1px más arriba, sobre esa línea; el indicador EVA mide 80% del tab (Figma ~86%); los divisores son pseudo-elementos (sin 1px propio). El cambio de tab (-active / aria-selected) y el desplazamiento horizontal (EVA mueve .tabs-nav con las flechas tabs-nav-arrow-first/last, ocultas en Figma) los maneja la app. Estructura: .sofia-ss (columna de 421px, --sofia-ss-width, a todo el alto del contenedor). El Content (.sofia-bs-content + .sofia-ss__body) es flex:1 y absorbe el alto libre: en Figma cada instancia tiene un alto fijo distinto (652/601/620/468/413px), que es exactamente lo que queda entre header y footer, por eso la preview coincide al alto del nodo. BS / Slot a 326px fijos centrado (en Figma también mide 326 a 421px; se sobreescribe el calc(100% + 6px) de bs-header solo dentro de .sofia-ss). Footer = Action Container Single CTA (.sofia-bs-footer--single, sin cambios): fila de 328px centrada a 46.5px, padding vertical --sofia-ss-spacing-action-v (16px), stroke superior Borders/medium y sombra static-hover (cae fuera del sheet). EVA: no se usa .eva-3-modal-side-sheet porque difiere del Figma (-sm 424px, header 24/24/20 con título 20/26, contenido 0 24 24, footer 20 24 24, overlay rgba(0,0,0,.8) y visibilidad por clases); los átomos sí son EVA. EVA prevalece en el CTA: el label queda ~1px más arriba (line-height EVA) y el borde izquierdo/derecho cae en medio píxel (46.5px).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-hero.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Side sheet · Desktop 1366 con overlay

side-sheet/ss-context1366 × 772px · stage #72777f
Viewport=1366x772Overlay=rgba(1,1,1,0.37)Side sheet=Title (421px, anclado a la derecha)

Ubicación del side sheet en desktop: frame de 1366x772 con overlay y el side sheet básico (ss-title) anclado al borde derecho a todo el alto.

Notas de implementación
Overlay = .sofia-overlay (posicionamiento absolute/inset 0 de bs-header) + .sofia-ss-overlay con el fill del frame "Modal" (4119:1825): rgba(1,1,1,0.37) (--sofia-ss-overlay-color), igual al dimmer del modal desktop y distinto del Overlay del bottom sheet (#323439). El side sheet (ss-title) lleva .sofia-ss--docked: absolute, top/right/bottom 0, ancho 421px. En la app usar position:fixed sobre el viewport. Overlay y sheet son hermanos para que js/motion/side-sheet.js los anime por separado: openSideSheet(sheetEl, overlayEl) / closeSideSheet(sheetEl, overlayEl) (translateX 100% → 0 + opacidad 0.8 → 1 en 340 ms cubic-bezier(0.32, 0.72, 0, 1); el overlay hace fade 0 → 1). Stage #72777f (Neutral-600) = lo que muestra el canvas de Figma debajo del overlay semitransparente; el compuesto da #484B50 como en el screenshot. Diferencia intencional: en Figma el Side_sheet mide 770px (hug) dentro del frame de 772px y deja ver 2px de overlay abajo; en código el sheet ocupa el 100% del alto y el Content crece 2px (el footer queda 2px más abajo). Estructura: .sofia-ss (columna de 421px, --sofia-ss-width, a todo el alto del contenedor). El Content (.sofia-bs-content + .sofia-ss__body) es flex:1 y absorbe el alto libre: en Figma cada instancia tiene un alto fijo distinto (652/601/620/468/413px), que es exactamente lo que queda entre header y footer, por eso la preview coincide al alto del nodo. BS / Slot a 326px fijos centrado (en Figma también mide 326 a 421px; se sobreescribe el calc(100% + 6px) de bs-header solo dentro de .sofia-ss). Footer = Action Container Single CTA (.sofia-bs-footer--single, sin cambios): fila de 328px centrada a 46.5px, padding vertical --sofia-ss-spacing-action-v (16px), stroke superior Borders/medium y sombra static-hover (cae fuera del sheet). EVA: no se usa .eva-3-modal-side-sheet porque difiere del Figma (-sm 424px, header 24/24/20 con título 20/26, contenido 0 24 24, footer 20 24 24, overlay rgba(0,0,0,.8) y visibilidad por clases); los átomos sí son EVA. EVA prevalece en el CTA: el label queda ~1px más arriba (line-height EVA) y el borde izquierdo/derecho cae en medio píxel (46.5px).
CSS requerido
  • EVA (CDN): eva-core.min.css + eva.min.css
  • Rubik (Google Fonts)
  • css/tokens.css
  • css/base.css
  • css/components/bs-chat.css
  • css/components/bs-footer.css
  • css/components/bs-header.css
  • css/components/bs-sheets.css
  • css/components/side-sheet.css

Motion · 3. Side Sheet · Desktop

Texto 4181:27785 · Qué evitar 4181:27789 · Código 4123:2476

¿Qué es?

Una capa que ocupa parte del ancho lateral de la pantalla. Se usa en desktop para contenido que complementa la vista principal sin reemplazarla: paneles de detalle, filtros avanzados, configuraciones, chats de soporte.

¿Qué queremos que comunique la animación?

Entrada: el panel desliza desde el borde lateral (derecho por defecto, izquierdo si aplica) hacia adentro de la pantalla. Refuerza la metáfora de que existe un espacio adyacente al viewport y el usuario lo está descubriendo o invocando. Es una extensión del canvas, no una interrupción.

Salida: el panel se retira hacia el mismo borde del que vino. El usuario entiende que el contenido "sigue estando ahí", fuera de la vista, y puede volver a invocarlo.

Qué evitar
  • No usar scale en el side sheet: destruye la metáfora espacial de panel lateral.
  • No animar el ancho del panel durante la entrada: el panel debe tener su ancho final desde el primer frame.
  • No usar el mismo timing corto del modal (260 ms): en desktop el recorrido es mayor y se ve apresurado.
3. Side Sheet · Desktop — referencia de Figma
image 3 · 4181:27787
Decisiones de diseño
VariableValorPor qué
Duración340 msEl más largo de los tres. El panel tiene más recorrido horizontal y una animación muy rápida se sentiría brusca en desktop.
Easingcubic-bezier(0.32, 0.72, 0, 1)Mismo spring que el bottom sheet. Ambos son "paneles que deslizan desde un borde"; la coherencia de curva refuerza el lenguaje de movimiento del sistema.
translateX inicio100% (right) / -100% (left)El panel parte desde exactamente fuera del viewport, sin peeks ni reveals parciales.
Opacidad inicio0.8Más visible que el bottom sheet desde el inicio, porque en desktop el movimiento lateral es menos pronunciado perceptualmente.
origin configurable"right" | "left"El panel derecho es el más común (detalle, configuración). El izquierdo se usa para navegación o contexto. La animación es especular; misma curva, dirección opuesta.
OverlayFade simple, más tenueEn desktop el overlay del side sheet suele ser más sutil que el de un modal, porque el contenido principal sigue siendo relevante.
Comportamiento esperado (origin: "right")
[VIEWPORT]                    [FUERA DEL VIEWPORT]
──────────────────────────── ┆ ─────────────────────
                             ┆
  contenido principal        ┆  ← sheet viene desde aquí
                             ┆
──────────────────────────── ┆ ─────────────────────

  Durante la animación:
──────────────────── ┌──────────────┐
  contenido          │              │
  principal          │  side sheet  │
  (con overlay)      │              │
──────────────────── └──────────────┘
Código literal del handoff · js/motion/side-sheet.js
js/motion/side-sheet.js
// ─── Side Sheet · Desktop ─────────────────────────────────────────────────────
// Desliza desde el borde derecho (o izquierdo pasando origin: "left").
// Uso:
//   openSideSheet(sheetEl, overlayEl, { origin: "right" | "left" })
//   closeSideSheet(sheetEl, overlayEl, { origin: "right" | "left" })

const SIDE_SHEET = {
  duration: 340,
  easing: "cubic-bezier(0.32, 0.72, 0, 1)",
};

function getTranslate(origin) {
  return origin === "left" ? "translateX(-100%)" : "translateX(100%)";
}

export function openSideSheet(sheet, overlay, { origin = "right" } = {}) {
  const offscreen = getTranslate(origin);

  overlay.style.display = "block";
  sheet.style.display   = "flex";

  overlay.animate(
    [{ opacity: 0 }, { opacity: 1 }],
    { duration: SIDE_SHEET.duration, easing: "ease", fill: "forwards" }
  );

  sheet.animate(
    [
      { transform: offscreen,        opacity: 0.8 },
      { transform: "translateX(0)",  opacity: 1   },
    ],
    { duration: SIDE_SHEET.duration, easing: SIDE_SHEET.easing, fill: "forwards" }
  );
}

export function closeSideSheet(sheet, overlay, { origin = "right" } = {}) {
  const offscreen = getTranslate(origin);

  const anim = sheet.animate(
    [
      { transform: "translateX(0)", opacity: 1   },
      { transform: offscreen,       opacity: 0.8 },
    ],
    { duration: SIDE_SHEET.duration, easing: SIDE_SHEET.easing, fill: "forwards" }
  );

  overlay.animate(
    [{ opacity: 1 }, { opacity: 0 }],
    { duration: SIDE_SHEET.duration, easing: "ease", fill: "forwards" }
  );

  anim.onfinish = () => {
    sheet.style.display   = "none";
    overlay.style.display = "none";
  };
}