Ir al contenido

Lineamientos de UI del iframe

Crowder es white-label: cada ticketera define su propia paleta y tipografía, y vos heredás ese tema visual dentro del iframe. Este documento no te prescribe colores ni fuentes — define solo el contrato del slot que se te garantiza y la geometría/forma recomendada para los componentes del iframe.

Tu iframe debe heredar el tema visual de la ticketera (paleta y tipografía) y aplicar las formas descritas acá. Para el protocolo de mensajes ver Implementación del iframe.

Lo que el host de Crowder en la página de la ticketera te garantiza dentro del iframe. Podés asumir estas condiciones como dadas.

El iframe se renderiza dentro de un contenedor .embedded-app-slot en la página de checkout/upsell. El layout padre tiene max-width: 1200px con margen lateral de 24px.

BreakpointAncho del slotLayout
Mobile (< 768px)100% del viewport (con padding lateral 16px)Una columna: iframe arriba, Resumen de compra como barra sticky al pie
Tablet (768–1024px)Fluido, entre 480 y 700pxDos columnas: iframe (flex 1) + Resumen (~340px)
Desktop (≥ 1024px)Fluido, entre 640 y 760pxDos columnas: iframe (flex 1) + Resumen (380px fijo). Gap 24px

El iframe es autosize: vos emitís un mensaje type: "display" con sizes.iframeHeight (px) que informa la altura de tu contenido. El host ajusta el <iframe> a ese valor, haciendo clamp con el min-height del slot. Resultado: el iframe crece según su contenido, sin scrollbar interno.

Breakpointmin-height (piso del host)
Mobile480px
Desktop / Tablet560px

Si el iframeHeight reportado es menor al piso, el host mantiene el min-height. Si es mayor, el iframe crece. Emití display al montar y cada vez que tu contenido cambia de altura (típicamente con un ResizeObserver sobre document.documentElement). Para conocer el viewport en sentido inverso (host → iframe) no necesitás postMessage: leé window.innerWidth/innerHeight directamente.

El host aplica:

.embedded-app-slot__iframe {
width: 100%;
border: 0;
border-radius: 12px; /* desktop / tablet */
background: transparent;
}

En mobile el border-radius baja a 0px (el iframe ocupa todo el ancho del viewport). Podés usar transparent en tu body para heredar el fondo del host, o pintar tu propio fondo con el token de superficie de la ticketera.

El fondo del iframe debe ser un color fijo, por defecto blanco. No lo cambies en automático con prefers-color-scheme: dark ni con ningún otro media query del sistema. Si la ticketera expone un token de superficie, usalo — pero como valor fijo, no como expresión que conmute con el modo del SO.

/* ✅ Correcto: color fijo */
body { background: #ffffff; }
/* ❌ Incorrecto: conmuta con el dark mode del SO */
@media (prefers-color-scheme: dark) {
body { background: #111; }
}

Por qué: el host de la ticketera define su propio tema visual y el iframe tiene que verse coherente con ese tema, no con la preferencia del SO del comprador. Un iframe que se pone oscuro mientras el resto del checkout sigue claro rompe la continuidad visual.

Tu contenido debe respetar 16px de padding contra los bordes del iframe (horizontal y vertical) en todos los breakpoints. El host no inyecta padding interno: si lo omitís, el contenido queda pegado a los costados y a la parte superior/inferior (especialmente notorio en mobile, donde el iframe va sin border-radius y a ancho completo del viewport). Aplicalo al contenedor raíz del body — no a cada componente — para mantener una columna visual consistente.

body {
padding: 16px;
}

El contenido del iframe ocupa el 100% del ancho disponible (slot menos los 16px de cada lado). El slot ya viene dimensionado por el host (entre 480 y 760px según breakpoint, ver tabla arriba), así que no agregues una max-width extra ni margin: auto para centrar la columna interna — eso deja franjas vacías a los costados y el formulario se ve angosto contra un slot ancho. El form, los inputs y el CTA deben extenderse de borde a borde del padding.

  • Sin header ni navbar de Crowder (el breadcrumb del evento vive en el host, arriba del slot).
  • Sin loading propio durante submit. Apenas Crowder dispara submit, el host pone el botón Continuar en estado de loading y bloquea visualmente la transición. El iframe no debe renderizar su propio spinner global ni overlay con loader — alcanza con deshabilitar inputs/botones para evitar edición concurrente (ver Implementación del iframe).
  • Sin disclaimer legal dentro del iframe — el texto que aclara al comprador que el producto adicional lo ofrece y administra un tercero (“Estos productos son ofrecidos y administrados por…”) lo renderiza Crowder fuera del iframe. No lo repitas adentro.
  • Sin paleta ni fuentes propias de Crowder. Tu iframe hereda del tema de la ticketera.
  • Sin CSS reset compartido.

Lo que el iframe NO debe renderizar (lo pone el host)

Sección titulada «Lo que el iframe NO debe renderizar (lo pone el host)»

Estos elementos viven fuera del iframe. Replicarlos adentro es un error: el usuario ve el mismo contenido dos veces y el flujo se rompe.

  • Título y subtítulo de la sección. El host imprime el nombre del bloque (ej. “Add-ons”) y su descripción corta (“Productos opcionales para tu experiencia”) arriba del iframe. No rendericés tu propio H1/H2 con el mismo concepto adentro.
  • Footer con sumatorio, total acumulado y botón “Continuar”. El total, el estado del carrito (“Ready to continue”) y el CTA que avanza al siguiente paso los renderiza el host (barra sticky en mobile, columna lateral en desktop). El iframe no debe incluir un footer con totales ni un botón propio para avanzar de paso — para avanzar usá el handshake submit / submitted documentado en Implementación del iframe.

Esta sección es recomendación, no requisito de protocolo. Define la geometría sin tocar color ni tipografía.

  1. Color y tipografía los provee la ticketera. Consumí los tokens visuales que la ticketera expone. Si no expone un token específico, usá currentColor, inherit o variables CSS con fallbacks neutrales.
  2. La forma es responsabilidad tuya y debe seguir esta guía para mantener consistencia con el resto del flujo de checkout.
  3. No reproduzcas branding de Crowder dentro del iframe. El iframe es tuyo y viste el tema de la ticketera.
Componenteborder-radiusNotas
Cards de contexto y agrupadoras8pxBorde izquierdo destacado de 3px solid (color primario del tema)
Sub-cards (ej. ítem de ticket)8pxBorde 1px en color de borde del tema, padding 12px 16px
Cards de producto (grilla de shop)4pxoverflow: hidden para que la imagen respete el radio
Chips de filtro de categoría8pxPadding 12px 20px, diferenciación por fondo
Rows de selección (lista)4pxSeparadas por border-bottom: 1px solid
Badges de tipo / tarifa4pxPadding 2px 8px, tipografía pequeña
Botón primario (CTA)6pxAltura 48px
Stepper numérico (— 0 +)3pxAltura 50px
Modal / dialog6px
Inputs / fields0 (sin redondeo)Underline-only: border-bottom: 1px solid, sin borde lateral ni superior, fondo transparente, height: 40px. Ver tab Inputs y formularios

Tipografía de descripciones e identificadores

Sección titulada «Tipografía de descripciones e identificadores»

Regla simple: sans para prosa, mono solo para identificadores opacos.

Tipo de contenidoClase / specCuándo
Descripción / texto explicativotext-sm text-muted-foreground (sans)Prosa que el comprador lee — descripciones de items, ayudas, leyendas
Metadata legible del itemtext-xs text-muted-foreground (sans)Palabras de dominio: sector, tarifa, fila, asiento, categoría
Identificadores opacostext-xs font-monoCadenas que el ojo necesita alinear o comparar carácter a carácter: UUID, short codes, número de orden, hash

Por qué: el monoespaciado aporta legibilidad técnica solo cuando hay caracteres a alinear o comparar. Para palabras de dominio (sector A, fila 12, Platea VIP) el mono se ve “técnico” sin razón y rompe la coherencia con el resto del item. Reservalo para los IDs reales.

Las cards que muestran información de contexto usan un borde izquierdo de 3px que indica el tipo de bloque:

  • Contexto provisto por Crowder (evento, tickets recibidos): color primario del tema.
  • Tu propia respuesta (ítems seleccionados): color de éxito del tema.

Resto de la card: border-radius: 8px, padding 20px, fondo en color de superficie del tema.

El context te llega con currency como código ISO 4217 (ARS, PEN, USD, BRL, etc.) y los price como number con hasta 2 decimales. No muestres el código crudo ni 15000.5 sin formatear: el iframe tiene que renderizar el monto con el símbolo, los separadores y los decimales que el usuario de esa plaza espera ver.

La forma simple y correcta de hacerlo es delegar en el browser:

format-money.js
const formatMoney = (amount, currency, locale) =>
new Intl.NumberFormat(locale, {
style: 'currency',
currency, // 'PEN', 'ARS', 'USD'...
}).format(amount);
// formatMoney(15000.5, 'PEN', 'es-PE') → "S/ 15,000.50"
// formatMoney(15000.5, 'ARS', 'es-AR') → "$ 15.000,50"
// formatMoney(15000, 'CLP', 'es-CL') → "$ 15.000"

Usá el locale del context para que separadores y posición del símbolo respeten la convención local. Si tenés que mostrar un monto antes de recibir el context, mostrá un skeleton — no inventes un símbolo.

Símbolo y decimales esperados por moneda. La columna Decimales es el minimumFractionDigits / maximumFractionDigits que debe ver el usuario (no la precisión del number que llega por el wire).

currencyPaísSímboloDecimalesEjemplo (15000.5)
ARSArgentina$2$ 15.000,50
BOBBoliviaBs2Bs 15.000,50
BRLBrasilR$2R$ 15.000,50
CLPChile$0$ 15.001
COPColombia$0$ 15.001
USDEcuadorUS$ / $2US$ 15,000.50
EURGuayana Francesa215.000,50 €
GYDGuyanaG$0G$ 15.001
PYGParaguay0₲ 15.001
PENPerúS/2S/ 15,000.50
SRDSurinam$2$ 15.000,50
UYUUruguay$U2$U 15.000,50
VESVenezuelaBs.2Bs. 15.000,50
  • Foco visible en todos los interactivos: :focus-visible con outline de 2px y outline-offset: 2px.
  • Targets táctiles ≥ 44×44px en mobile.
  • prefers-reduced-motion: reduce debe deshabilitar animaciones no esenciales.
  • Idioma del contenido alineado al locale del payload.
  • Contraste: sos responsable de respetar WCAG AA al aplicar la paleta de la ticketera (texto 4.5:1, componentes 3:1).