AspectRatio
A layout component that maintains a consistent aspect ratio for its children, commonly used for responsive media elements like images, videos, and iframes.
Overviewβ
Use AspectRatio when you needβ¦
- Responsive images or videos that maintain their aspect ratio
- Consistent placeholder dimensions before media loads
- Video embeds (YouTube, Vimeo) that scale properly
- Square avatars or profile pictures
- Card thumbnails with consistent dimensions
- Map containers or canvas elements with fixed ratios
Anatomyβ
AspectRatio creates a container with padding-based aspect ratio control and absolutely positioned children.
βββββββββββββββββββββββββββββββββββββββββββ
β AspectRatio Container (position: rel) β
β β
β βββββββββββββββββββββββββββββββββββ β
β β β β
β β Child (position: absolute) β β
β β width: 100%, height: 100% β β
β β β β
β βββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββ
Component Structure:
- Container: Relative positioned wrapper with padding-bottom percentage
- Child wrapper: Absolutely positioned, fills container (0,0 to 100%,100%)
- Content: Renders inside absolutely positioned wrapper
How it works:
The component uses the "padding-bottom hack" technique:
- Container has
position: relativeandpadding-bottom: (1/ratio) Γ 100% - For ratio
16/9, padding-bottom is56.25%(9/16 Γ 100%) - For ratio
4/3, padding-bottom is75%(3/4 Γ 100%) - Children are absolutely positioned to fill the space
Usageβ
Import the component:
import { AspectRatio } from '@grasdouble/lufa_design-system';
Basic usageβ
import { AspectRatio } from '@grasdouble/lufa_design-system';
function App() {
return (
<>
<AspectRatio ratio={16 / 9}>
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="Video"
style={{ width: '100%', height: '100%', border: 'none' }}
/>
</AspectRatio>
<AspectRatio ratio={4 / 3}>
<img src="/hero.jpg" alt="Hero" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
</AspectRatio>
<AspectRatio ratio={1}>
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#f3f4f6',
}}
>
Square content
</div>
</AspectRatio>
</>
);
}
Pattern: Video embedβ
import { AspectRatio } from '@grasdouble/lufa_design-system';
export function VideoEmbed() {
return (
<div style={{ maxWidth: '800px' }}>
<AspectRatio ratio={16 / 9}>
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="YouTube video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
style={{ width: '100%', height: '100%', border: 'none', borderRadius: '8px' }}
/>
</AspectRatio>
</div>
);
}
Pattern: Image galleryβ
import { AspectRatio } from '@grasdouble/lufa_design-system';
const images = [
{ id: 1, src: '/photo1.jpg', alt: 'Photo 1' },
{ id: 2, src: '/photo2.jpg', alt: 'Photo 2' },
{ id: 3, src: '/photo3.jpg', alt: 'Photo 3' },
];
export function ImageGallery() {
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '16px' }}>
{images.map((image) => (
<AspectRatio key={image.id} ratio={1}>
<img
src={image.src}
alt={image.alt}
style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: '8px' }}
/>
</AspectRatio>
))}
</div>
);
}
Variantsβ
Common aspect ratiosβ
| Ratio | Value | Description | Use Case |
|---|---|---|---|
| 16:9 | 16/9 | Widescreen (1.778) | Videos, modern displays |
| 4:3 | 4/3 | Classic (1.333) | Old TV, presentations |
| 3:2 | 3/2 | Photography (1.5) | DSLRs, print photos |
| 1:1 | 1 | Square (1.0) | Avatars, Instagram |
| 9:16 | 9/16 | Portrait (0.5625) | Stories, vertical video |
| 21:9 | 21/9 | Ultrawide (2.333) | Cinema, gaming monitors |
Propsβ
| Prop | Type | Default | Description |
|---|---|---|---|
ratio | number | 16/9 | Aspect ratio (width/height) |
as | 'div' | 'figure' | 'section' | 'article' | 'div' | HTML element to render |
children | ReactNode | - | Content to render with constrained ratio |
className | string | - | Additional CSS classes |
Also supports all standard HTML attributes for the underlying element.
Accessibilityβ
AspectRatio is a non-interactive layout container. Ensure child content (images, videos) has proper accessibility attributes.
import { AspectRatio } from '@grasdouble/lufa_design-system';
export function AccessibleVideo() {
return (
<AspectRatio ratio={16 / 9}>
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="Rick Astley - Never Gonna Give You Up (Official Video)"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
style={{ width: '100%', height: '100%', border: 'none' }}
/>
</AspectRatio>
);
}
- Always provide
alttext for images. - Include descriptive
titlefor iframes. - Use semantic HTML (
figure,img,video). - Ensure sufficient color contrast for overlays.
- Use
objectFit: 'cover'orobjectFit: 'contain'for images.
Theming & Tokensβ
AspectRatio uses CSS-only techniques (no design tokens). The component applies a padding-bottom percentage calculated from the ratio prop.
/* Example: ratio={16/9} */
.aspect-ratio-container {
position: relative;
padding-bottom: 56.25%; /* (9/16) Γ 100% */
}
.aspect-ratio-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
Performance characteristics:
- No resize observers or event listeners
- No layout reflows during rendering
- Minimal CSS classes applied
- No inline style computations (except for custom ratios)
Do / Don'tβ
- Use
objectFit: 'cover'orobjectFit: 'contain'for images to prevent distortion - Ensure child elements have
width: 100%andheight: 100%to fill the container - Use semantic HTML elements via the
asprop for figures and sections - Provide descriptive alt text for images and titles for iframes
- Use common ratio constants (
16/9,4/3,1) for consistency
- Don't omit alt text on images
- Don't use AspectRatio for text-only content (unnecessary overhead)
- Don't nest AspectRatio components
- Don't pass ratio as a string (use numeric division like
16/9) - Don't forget to handle loading states for media
Related Componentsβ
- Box - Basic layout container for padding, margins, and backgrounds
- Center - Centers content horizontally and vertically
- Container - Max-width container with responsive padding
- Bleed - Breaks out of container constraints for full-width content