HextaUI

Aspect ratio

Uma caixa que mantém sua forma antes de a mídia carregar, exibe um shimmer durante o carregamento, faz a mídia aparecer com fade e usa um fallback quando ela falha.

Sunset over mountains
import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioDemo() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} className="rounded-xl">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
      </AspectRatio>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/aspect-ratio.json

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

import { AspectRatio } from "@/components/ui/aspect-ratio"
<AspectRatio ratio={16 / 9} className="rounded-lg">
  <img src="/photo.jpg" alt="Sunset over mountains" />
</AspectRatio>

Proporções

ratio aceita um número, uma string "w/h" ou uma string "w:h".

import {
  AspectRatio,
  type AspectRatioValue,
} from "@/components/ui/aspect-ratio"

const ratios: { label: string; ratio: AspectRatioValue }[] = [
  { label: "16 / 9", ratio: 16 / 9 },
  { label: "1", ratio: 1 },
  { label: '"4/3"', ratio: "4/3" },
  { label: '"21:9"', ratio: "21:9" },
]

export function AspectRatioRatios() {
  return (
    <div className="grid w-full max-w-md grid-cols-2 gap-3">
      {ratios.map(({ label, ratio }) => (
        <div key={label} className="flex flex-col gap-1.5">
          <AspectRatio ratio={ratio} className="rounded-lg">
            <img src="/preview/landscape.svg" alt="Sunset over mountains" />
          </AspectRatio>
          <span className="font-mono text-xs text-muted-foreground">
            {label}
          </span>
        </div>
      ))}
    </div>
  )
}

Carregamento lento

A caixa mantém sua forma e exibe o shimmer até a imagem chegar, e então a imagem aparece com fade, para que nada abaixo se mova. Pressione Reload para ver de novo.

"use client"

import * as React from "react"

import { AspectRatio } from "@/components/ui/aspect-ratio"
import { Button } from "@/components/ui/button"

export function AspectRatioSlowLoad() {
  const [src, setSrc] = React.useState<string | undefined>(undefined)
  const [attempt, setAttempt] = React.useState(0)

  React.useEffect(() => {
    const timer = setTimeout(
      () => setSrc(`/preview/landscape.svg?v=${attempt}`),
      1500
    )
    return () => clearTimeout(timer)
  }, [attempt])

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <AspectRatio ratio={16 / 9} className="rounded-xl">
        <img src={src} alt="Sunset over mountains" />
      </AspectRatio>
      <Button
        variant="outline"
        size="sm"
        className="self-start"
        onClick={() => {
          setSrc(undefined)
          setAttempt(attempt + 1)
        }}
      >
        Reload
      </Button>
    </div>
  )
}

Imagem quebrada

Quando a imagem falha, o glifo de imagem quebrada do navegador é ocultado e um ícone de fallback é exibido no lugar. Passe fallback para substituí-lo, ou fallback={null} para não exibir nada.

import { IconMoodSad } from "@tabler/icons-react"

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioBrokenImage() {
  return (
    <div className="grid w-full max-w-md grid-cols-2 gap-3">
      <AspectRatio ratio={4 / 3} className="rounded-lg">
        <img src="/preview/does-not-exist.jpg" alt="Team photo" />
      </AspectRatio>
      <AspectRatio
        ratio={4 / 3}
        className="rounded-lg"
        fallback={
          <span className="flex flex-col items-center gap-1 text-xs">
            <IconMoodSad />
            Couldn’t load
          </span>
        }
      >
        <img src="/preview/does-not-exist.jpg" alt="Team photo" />
      </AspectRatio>
    </div>
  )
}

Sobreposição

Filhos com posição absoluta ficam sobre a mídia. A caixa não corta nada, então os anéis de foco em links de sobreposição continuam visíveis.

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioOverlay() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} className="rounded-xl">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
        <div className="absolute inset-x-3 bottom-3 flex items-center justify-between gap-3 rounded-lg bg-background/80 px-3 py-2 text-sm backdrop-blur-sm">
          <span className="truncate font-medium">Dolomites at dusk</span>
          <a
            href="#"
            className="shrink-0 rounded-sm underline underline-offset-4 outline-none focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden"
          >
            View
          </a>
        </div>
      </AspectRatio>
    </div>
  )
}

Sem placeholder

placeholder={false} desativa o shimmer de carregamento, o fade-in e o fallback, para o comportamento padrão do shadcn.

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioWithoutPlaceholder() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} placeholder={false} className="rounded-lg">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
      </AspectRatio>
    </div>
  )
}

Responsivo

Substitua a proporção em um breakpoint com uma classe aspect. Esta é quadrada em telas pequenas e md:aspect-video a partir de md.

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioResponsive() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={1} className="rounded-lg md:aspect-video">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
      </AspectRatio>
    </div>
  )
}

Dentro de uma coluna flex centralizada

A caixa tem largura total por padrão, então preenche a coluna em vez de colapsar a zero quando o pai centraliza seus filhos.

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioFlexColumn() {
  return (
    <div className="flex w-full max-w-md flex-col items-center rounded-lg border border-dashed p-3">
      <AspectRatio ratio={16 / 9} className="rounded-lg">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
      </AspectRatio>
    </div>
  )
}

Conteúdo de texto

Filhos que não são mídia recebem a caixa e nada mais. Posicione-os você mesmo.

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioTextContent() {
  return (
    <div className="w-full max-w-md">
      <AspectRatio ratio={3} className="rounded-lg border">
        <div className="absolute inset-0 grid place-items-center p-4 text-center text-sm text-muted-foreground">
          Any content can sit in the box.
        </div>
      </AspectRatio>
    </div>
  )
}

Como figure

Mantenha as legendas fora da caixa para que não alterem a proporção.

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioFigure() {
  return (
    <figure className="flex w-full max-w-md flex-col gap-2">
      <AspectRatio ratio={16 / 9} className="rounded-lg">
        <img src="/preview/landscape.svg" alt="Sunset over mountains" />
      </AspectRatio>
      <figcaption className="text-xs text-muted-foreground">
        Dolomites at dusk, photographed from Seceda.
      </figcaption>
    </figure>
  )
}

Proporção inválida

0, números negativos e strings que não podem ser interpretadas recorrem a um quadrado e registram um aviso em desenvolvimento.

import {
  AspectRatio,
  type AspectRatioValue,
} from "@/components/ui/aspect-ratio"

export function AspectRatioInvalidRatio() {
  return (
    <div className="grid w-full max-w-md grid-cols-2 gap-3">
      <AspectRatio ratio={0} className="rounded-lg border" />
      <AspectRatio
        ratio={"abc" as AspectRatioValue}
        className="rounded-lg border"
      />
    </div>
  )
}

Da direita para a esquerda

Sobreposições posicionadas com propriedades lógicas como start-3 seguem a direção de leitura.

import { AspectRatio } from "@/components/ui/aspect-ratio"

export function AspectRatioRtl() {
  return (
    <div dir="rtl" className="w-full max-w-md">
      <AspectRatio ratio={16 / 9} className="rounded-lg">
        <img src="/preview/landscape.svg" alt="غروب الشمس فوق الجبال" />
        <span className="absolute start-3 top-3 rounded-md bg-background/80 px-2 py-1 text-xs">
          جديد
        </span>
      </AspectRatio>
    </div>
  )
}
  • A caixa fica com aria-busy enquanto sua mídia carrega.
  • O fallback é decorativo e fica oculto para tecnologias assistivas. O texto alt da imagem continua disponível quando ela falha ao carregar, então sempre escreva um.
  • Com movimento reduzido ativado, a mídia aparece sem fade.

Aceita todos os atributos do elemento que renderiza. Mídia colocada diretamente dentro, um <img>, <picture> ou <video>, preenche a caixa com object-cover e herda seu raio.

PropTipoPadrão
ratio
number | `${number}/${number}` | `${number}:${number}`1
placeholderExibe um shimmer enquanto a mídia carrega e um fallback quando ela falha.
booleantrue
fallbackExibido quando a mídia falha. null não exibe nada.
ReactNode<IconPhotoOff />
render
ReactElement | (props, state) => ReactElement<div>
AtributoDescrição
data-slot="aspect-ratio"Seleciona a caixa no CSS.
data-stateloading, loaded ou error. Definido apenas quando placeholder está ativado e a caixa contém mídia.
aria-busyPresente enquanto a mídia carrega.
--ratioA proporção interpretada como número.
data-slot="aspect-ratio-placeholder"O shimmer exibido durante o carregamento ou após um erro.
data-slot="aspect-ratio-fallback"O wrapper em volta do fallback.

parseAspectRatio(ratio) converte qualquer proporção aceita em número, recorrendo a 1. Use-o para dimensionar outros elementos da mesma forma. Os tipos AspectRatioValue e AspectRatioProps também são exportados.