Components (registry)Actions
ReviewCard
Testimonio de cliente: la cita y quien la firma, como figure + blockquote + figcaption. Pensada para tiras horizontales donde varias se leen de pasada.
Anatomía CSS
<div class="review-card"> <span class="review-card__author"></span> <span class="review-card__name"></span> <span class="review-card__quote"></span> <span class="review-card__role"></span> </div>
| Clase | Propósito |
|---|---|
review-card | root |
review-card__author | element |
review-card__name | element |
review-card__quote | element |
review-card__role | element |
Tokens resueltos
Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.
| Variante | Prop | Valor | Token |
|---|---|---|---|
| all | bg | #ffffff | card |
| all | fg | #525252 | muted.foreground |
| all | border | 0 | (unparsed) |
CSS mínimo funcional
Autocontenido: sin imports ni tokens. Para previews y prototipos — en producción se consume el CSS del DS (@atom-uikit/css/components.css</code>, <code>@atom-uikit/tokens/tokens.css).
/* -------------------------------------------------------------------------
Review Card
Testimonio de cliente: la cita y quien la firma. Pensada para tiras
horizontales (marquee) donde varias se leen de pasada.
Estructura: .review-card <figure>
> .review-card__quote <blockquote>
> .review-card__author <figcaption>
> .review-card__name
> .review-card__role
SEMANTICA, y no es decoracion: un testimonio es una cita con atribucion, asi
que el par correcto es <figure><blockquote> + <figcaption>. <blockquote> a
secas NO admite <cite> como hermano dentro de si mismo — la atribucion vive
FUERA de la cita, y <figure> es lo que las une. Con divs sueltos un lector de
pantalla no puede anunciar "cita" ni decir de quien es.
La cita va PRIMERO y la atribucion despues: se lee la frase y luego quien la
dijo. Al reves obliga a encabezar un blockquote que todavia no ha empezado.
Las comillas las pone el CSS (::before/::after con open-quote), no el
contenido. Escribirlas a mano acaba mezclando " rectas y “ tipograficas
segun quien edite, y ademas un lector de pantalla las deletrea.
------------------------------------------------------------------------- */
.review-card {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
width: 24rem;
/* Todas las cards de una fila miden lo mismo: la mas alta manda y el resto
rellena. Con altura por contenido, cada atribucion cae donde acabe su cita
y la fila se lee como un serrucho. */
height: 100%;
/* Se estira ella misma en vez de pedirle al carril que cambie: .marquee__item
va en align-items:center y es una clase COMPARTIDA con la tira de features.
align-self gana sobre el align-items del padre sin tocar a nadie mas. */
align-self: stretch;
margin: 0;
padding: 32px;
border-radius: 16px;
background-color: #ffffff;
color: #0a0a0a;
font-family: 'inter tight', -apple-system, blinkmacsystemfont, 'segoe ui', roboto, helvetica, arial, sans-serif, ui-sans-serif, system-ui, sans-serif;
/* .marquee__item va en nowrap para tiras de texto; aqui la cita es parrafo. */
white-space: normal;
text-align: left;
}
.review-card__quote {
/* <blockquote> llega con estilos de TAG puestos por el navegador y por el
host: Webflow le da border-left gris y padding propios. Como esos van por
etiqueta y esto va por clase, la clase gana — pero SOLO en lo que declara.
Sin resetear borde y padding aqui, la cita sale con una barra vertical
gris a la izquierda que nadie pidio. */
border: 0;
margin: 0;
padding: 0;
width: 100%;
text-align: left;
font-size: 20px;
font-weight: 400;
line-height: 1.45;
letter-spacing: -0.01em;
text-wrap: pretty;
}
/* Las comillas cuelgan del flujo para que la primera letra siga alineada con
el resto de la card: sin esto la cita entera parece sangrada un caracter. */
.review-card__quote::before {
content: open-quote;
margin-inline-start: -0.4em;
}
.review-card__quote::after {
content: close-quote;
}
.review-card__author {
display: flex;
width: 100%;
flex-direction: column;
align-items: flex-start;
gap: 4px;
text-align: left;
}
.review-card__name {
font-size: 16px;
font-weight: 600;
line-height: 1.5;
}
.review-card__role {
color: #525252;
font-size: 12.8px;
font-weight: 400;
line-height: 1.45;
}
/* El eslabon que falta: la lista centra sus items, asi que sin esto el item se
ajusta al contenido y el height:100% de la card no tiene referencia. Se hace
con :has para que el marquee siga sin saber nada de review-card — la regla la
trae quien la necesita.
align-SELF, no align-items: align-items alinearia a los HIJOS del item, que no
es el problema; lo que hace falta es que el item se estire EL a la altura de
la fila. Con align-items el item seguia midiendo su contenido y las cards
quedaban desiguales igual. */
.marquee__item:has(> .review-card) {
align-self: stretch;
}
@media (max-width: 767px) {
.review-card {
/* En movil la card ocupa casi todo el carril: el ancho fijo de escritorio
dejaria una segunda card asomando media pantalla. */
width: 18rem;
padding: 24px;
}
}
Codigo fuente
styles/components/review-card.css
/* ------------------------------------------------------------------------- Review Card Testimonio de cliente: la cita y quien la firma. Pensada para tiras horizontales (marquee) donde varias se leen de pasada. Estructura: .review-card <figure> > .review-card__quote <blockquote> > .review-card__author <figcaption> > .review-card__name > .review-card__role SEMANTICA, y no es decoracion: un testimonio es una cita con atribucion, asi que el par correcto es <figure><blockquote> + <figcaption>. <blockquote> a secas NO admite <cite> como hermano dentro de si mismo — la atribucion vive FUERA de la cita, y <figure> es lo que las une. Con divs sueltos un lector de pantalla no puede anunciar "cita" ni decir de quien es. La cita va PRIMERO y la atribucion despues: se lee la frase y luego quien la dijo. Al reves obliga a encabezar un blockquote que todavia no ha empezado. Las comillas las pone el CSS (::before/::after con open-quote), no el contenido. Escribirlas a mano acaba mezclando " rectas y “ tipograficas segun quien edite, y ademas un lector de pantalla las deletrea. ------------------------------------------------------------------------- */ .review-card { display: flex; flex-direction: column; align-items: flex-start; justify-content: space-between; gap: var(--spacing-6); width: var(--review-card-width, 24rem); /* Todas las cards de una fila miden lo mismo: la mas alta manda y el resto rellena. Con altura por contenido, cada atribucion cae donde acabe su cita y la fila se lee como un serrucho. */ height: 100%; /* Se estira ella misma en vez de pedirle al carril que cambie: .marquee__item va en align-items:center y es una clase COMPARTIDA con la tira de features. align-self gana sobre el align-items del padre sin tocar a nadie mas. */ align-self: stretch; margin: 0; padding: var(--spacing-8); border-radius: var(--radius-xl); background-color: var(--card); color: var(--card-foreground); font-family: var(--font-family-sans), ui-sans-serif, system-ui, sans-serif; /* .marquee__item va en nowrap para tiras de texto; aqui la cita es parrafo. */ white-space: normal; text-align: left; } .review-card__quote { /* <blockquote> llega con estilos de TAG puestos por el navegador y por el host: Webflow le da border-left gris y padding propios. Como esos van por etiqueta y esto va por clase, la clase gana — pero SOLO en lo que declara. Sin resetear borde y padding aqui, la cita sale con una barra vertical gris a la izquierda que nadie pidio. */ border: 0; margin: 0; padding: 0; width: 100%; text-align: left; font-size: var(--font-size-lg); font-weight: var(--font-weight-regular); line-height: var(--line-height-lg); letter-spacing: var(--letter-spacing-snug); text-wrap: pretty; } /* Las comillas cuelgan del flujo para que la primera letra siga alineada con el resto de la card: sin esto la cita entera parece sangrada un caracter. */ .review-card__quote::before { content: open-quote; margin-inline-start: -0.4em; } .review-card__quote::after { content: close-quote; } .review-card__author { display: flex; width: 100%; flex-direction: column; align-items: flex-start; gap: var(--spacing-1); text-align: left; } .review-card__name { font-size: var(--font-size-base); font-weight: var(--font-weight-semibold); line-height: var(--line-height-base); } .review-card__role { color: var(--muted-foreground); font-size: var(--font-size-sm); font-weight: var(--font-weight-regular); line-height: var(--line-height-sm); } /* El eslabon que falta: la lista centra sus items, asi que sin esto el item se ajusta al contenido y el height:100% de la card no tiene referencia. Se hace con :has para que el marquee siga sin saber nada de review-card — la regla la trae quien la necesita. align-SELF, no align-items: align-items alinearia a los HIJOS del item, que no es el problema; lo que hace falta es que el item se estire EL a la altura de la fila. Con align-items el item seguia midiendo su contenido y las cards quedaban desiguales igual. */ .marquee__item:has(> .review-card) { align-self: stretch; } @media (max-width: 767px) { .review-card { /* En movil la card ocupa casi todo el carril: el ancho fijo de escritorio dejaria una segunda card asomando media pantalla. */ width: var(--review-card-width-mobile, 18rem); padding: var(--spacing-6); } }