Skip to main content

AspectRatio

πŸŒ“Dark Mode Compatible

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
Live demoInteractive
16:9 Default Aspect Ratio

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: relative and padding-bottom: (1/ratio) Γ— 100%
  • For ratio 16/9, padding-bottom is 56.25% (9/16 Γ— 100%)
  • For ratio 4/3, padding-bottom is 75% (3/4 Γ— 100%)
  • Children are absolutely positioned to fill the space

Usage​

Import the component:

import { AspectRatio } from '@grasdouble/lufa_design-system';

Basic usage​

src/App.tsx
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​

src/components/VideoEmbed.tsx
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>
);
}
src/components/ImageGallery.tsx
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​

RatioValueDescriptionUse Case
16:916/9Widescreen (1.778)Videos, modern displays
4:34/3Classic (1.333)Old TV, presentations
3:23/2Photography (1.5)DSLRs, print photos
1:11Square (1.0)Avatars, Instagram
9:169/16Portrait (0.5625)Stories, vertical video
21:921/9Ultrawide (2.333)Cinema, gaming monitors

Props​

PropTypeDefaultDescription
rationumber16/9Aspect ratio (width/height)
as'div' | 'figure' | 'section' | 'article''div'HTML element to render
childrenReactNode-Content to render with constrained ratio
classNamestring-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 alt text for images.
  • Include descriptive title for iframes.
  • Use semantic HTML (figure, img, video).
  • Ensure sufficient color contrast for overlays.
  • Use objectFit: 'cover' or objectFit: '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​

Do
  • Use objectFit: 'cover' or objectFit: 'contain' for images to prevent distortion
  • Ensure child elements have width: 100% and height: 100% to fill the container
  • Use semantic HTML elements via the as prop 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
  • 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
  • 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