ATOM
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.

Preview

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>
ClasePropósito
review-cardroot
review-card__authorelement
review-card__nameelement
review-card__quoteelement
review-card__roleelement

Tokens resueltos

Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.

VariantePropValorToken
allbg#ffffffcard
allfg#525252muted.foreground
allborder0(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);
  }
}

On this page

Detalles

Publicado14 de mayo de 2026
Categoriaactions
Lectura...
Visitas...
Ayuda?Slack

Componente

ReviewCard

Source

components-react / cssDisponible via MCP: atom_uikit_source("review-card")