Aspect ratio
A box that holds its shape before media loads, shimmers while loading, fades the media in and falls back when it fails.
pnpm dlx shadcn@latest add https://hextaui.com/r/aspect-ratio.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cnCopy and paste the following code into your project.
components/ui/aspect-ratio.tsx components/ui/skeleton.tsx Update the import paths to match your project setup.
Ratios
ratio takes a number, a "w/h" string or a "w:h" string.
Slow load
The box holds its shape and shimmers until the image arrives, then the image fades in, so nothing below it moves. Press Reload to watch it again.
Broken image
When the image fails, the browser’s broken-image glyph is hidden and a fallback icon is shown instead. Pass fallback to replace it, or fallback={null} to show nothing.


Overlay
Absolutely positioned children sit on top of the media. The box clips nothing, so focus rings on overlay links stay visible.
Without placeholder
placeholder={false} turns off the loading shimmer, the fade-in and the fallback, for the plain shadcn behavior.
Responsive
Override the ratio at a breakpoint with an aspect class. This one is square on small screens and md:aspect-video from md up.
Inside a centered flex column
The box is full width by default, so it fills the column instead of collapsing to zero when the parent centers its children.
Text content
Children that aren’t media get the box and nothing else. Position them yourself.
As a figure
Keep captions outside the box so they don’t change the ratio.
Invalid ratio
0, negative numbers and unparsable strings fall back to a square and log a warning in development.
Right to left
Overlays positioned with logical properties like start-3 follow the reading direction.
- The box is
aria-busywhile its media loads. - The fallback is decorative and hidden from assistive tech. The image’s
alttext stays available when it fails to load, so always write one. - With reduced motion on, media appears without fading.
Accepts every attribute of the element it renders. Media placed directly inside, an <img>, <picture> or <video>, fills the box with object-cover and inherits its radius.
| Prop | Type | Default |
|---|---|---|
ratio | number | `${number}/${number}` | `${number}:${number}` | 1 |
placeholderShows a shimmer while the media loads and a fallback when it fails. | boolean | true |
fallbackShown when the media fails. null shows nothing. | ReactNode | <IconPhotoOff /> |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="aspect-ratio" | Target the box in CSS. |
data-state | loading, loaded or error. Set only when placeholder is on and the box holds media. |
aria-busy | Present while the media loads. |
--ratio | The parsed ratio as a number. |
data-slot="aspect-ratio-placeholder" | The shimmer shown while loading or after an error. |
data-slot="aspect-ratio-fallback" | The wrapper around the fallback. |
parseAspectRatio(ratio) turns any accepted ratio into a number, falling back to 1. Use it to size other elements the same way. The AspectRatioValue and AspectRatioProps types are exported too.