AspectRatio
Keeps a box at a fixed proportion, like 16:9 or 1:1, at any width. Use it for videos, embeds, maps and thumbnails, so the space is reserved before the media loads and every card in a row lines up.
Installation
Set up Camaleo once (Installation), then import it. It has no state, so it renders on the server.
import { AspectRatio } from "@camaleo/react";Anatomy
One box with position: relative and hidden overflow. A direct img, video or iframe fills it; anything else you lay on top with position: absolute.
Examples
Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.
Module 2 · Lesson 4
ratio={16 / 9}Pick a ratio
Switch the ratio. ratio is width divided by height, so write it as a fraction: 16 / 9 for video, 4 / 3 for photos, 1 for squares, 3 / 4 for portraits.
cover or contain
cover, the default, fills the box and crops what doesn't fit. Set objectFit: "contain" on the image when every edge matters, like a product or a logo.
Cards with photos of any shape
The three photos are 16:9, 4:3 and 3:4. Cut to the same ratio, the cards are the same height and the titles line up.
Thumbnail grid
Square thumbnails make a tidy grid whatever was uploaded. object-position chooses which part of the photo survives the crop.
Rua Augusta 1200, São Paulo
Embeds need a title
An iframe fills the box like an image does. Give it a title that says what's inside: it is the frame's name for screen readers.
Casa do Farol, Ilhabela. This line stays put while the photo loads.
Space kept while loading
Reload the photo. The box has its height before the file arrives, so the text below never jumps, and a status message says when it's ready.
In context
Media rarely stands alone. These are whole pieces of UI you can copy, in the skin you picked.
Guidelines
Photos of any shape end up the same size, and the titles line up.
The row goes ragged and the eye follows the gaps instead of the content.
Their edges carry meaning; show all of it, with some padding.
The crop cuts off the brand name, which is the whole point.
Props
16 / 9 for video, 4 / 3 for photos, 1 for a square.Every attribute of a <div> passes through, like style and className. Set a width (or let the parent's grid set it); the height always follows the ratio.
Styling hooks
Restyle it without forking: target the slot, or set the custom property in your own CSS.
Attributes
Tokens it reads
This component only
/* wider video on large screens, taller on phones */
.lesson [data-slot="aspect-ratio"] {
--cml-aspect-ratio: 4 / 3;
}
@media (min-width: 48rem) {
.lesson [data-slot="aspect-ratio"] {
--cml-aspect-ratio: 16 / 9;
}
}Accessibility
- The box adds no role and no name. What's inside still needs one: alt on every image that means something, alt="" on decoration.
- Every iframe needs a title that says what it holds ("Map: Rua Augusta 1200"), or screen readers only announce "frame".
- A play button laid over a poster is an icon-only button: give it a name that includes the video's title.
- Cropping with cover can cut off text or faces. Don't put information in the edges of an image that might be cropped.