Responsive Design, Media Queries, and Container Queries
Build responsive layouts using breakpoint tokens, reusable media-query mixins, mobile-first CSS, fluid typography, and modern container queries.
Mobile-First Design with Sass
Mobile-first styling begins with a layout suitable for smaller viewports and adds enhancements as more space becomes available. This approach encourages a usable baseline and avoids unnecessarily complex overrides.
Sass can centralize breakpoint tokens and provide mixins that make media-query usage consistent. Breakpoints should reflect where the content or layout stops working well, rather than being chosen only to match specific device names.
A responsive design should consider more than width. Accessibility, touch targets, readable typography, overflow, orientation, and user preferences can all influence the final CSS.
$tablet: 48rem;
$desktop: 72rem;
.card-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
@media (min-width: $tablet) {
.card-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: $desktop) {
.card-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}minmax(0, 1fr) pattern allows grid tracks to shrink below their intrinsic minimum size, helping prevent long content from forcing unexpected overflow.What is the defining characteristic of a mobile-first stylesheet?
Reusable Breakpoint Mixins
A breakpoint map and a responsive mixin can standardize media-query usage across a codebase. This avoids scattered breakpoint values and makes the intended responsive behavior more visible.
A map can associate semantic names such as tablet and desktop with width values. A mixin can validate the supplied name, retrieve its value, and emit a media query using @content.
The breakpoint names should describe the layout transition rather than an assumed physical device. If the content requires a new intermediate layout, add a breakpoint based on the actual design requirement.
@use "sass:map";
$breakpoints: (
tablet: 48rem,
desktop: 72rem,
wide: 90rem
);
@mixin up($name) {
$value: map.get($breakpoints, $name);
@if $value == null {
@error "Unknown breakpoint: #{$name}";
}
@media (min-width: $value) {
@content;
}
}
.layout {
display: grid;
grid-template-columns: 1fr;
@include up(tablet) {
grid-template-columns: 14rem 1fr;
}
}Why is it useful to validate a breakpoint name before generating a media query?
Fluid Typography and Responsive Spacing
Fluid design allows dimensions to change smoothly as the available viewport changes. CSS clamp() is particularly useful for typography and spacing because it defines a minimum, preferred value, and maximum.
Sass can calculate fixed design tokens or generate reusable CSS expressions. The browser evaluates viewport-dependent expressions at runtime.
Fluid values should be tested across a range of viewport sizes. Excessively aggressive scaling can make text difficult to read or produce layouts that look unbalanced on intermediate screens.
.hero-title {
font-size: clamp(2rem, 1.2rem + 3vw, 4.5rem);
line-height: 1.1;
max-width: 16ch;
}
.section {
padding-block: clamp(2rem, 5vw, 5rem);
padding-inline: clamp(1rem, 3vw, 2.5rem);
}ch unit provides a width based on the advance measure of the 0 glyph in the current font. It can help control line length, although it does not guarantee an exact number of words per line.What does the third argument in `clamp(minimum, preferred, maximum)` represent?
Container Queries and Component-Level Responsiveness
Viewport media queries respond to the viewport or another media feature. Container queries allow a component to respond to the size of a designated containing element.
This distinction matters in component-based applications. A card might appear in a wide dashboard column or a narrow sidebar without changing its component implementation. A container query lets the card adapt to the space actually available to it.
Sass can organize and reuse container-query declarations, but container-query behavior itself is a native CSS feature. Use container-type to establish an appropriate query container and write @container rules for the desired conditions.
.card-list {
container-type: inline-size;
}
.product-card {
display: grid;
gap: 1rem;
}
@container (min-width: 32rem) {
.product-card {
grid-template-columns: 10rem 1fr;
align-items: center;
}
}.card-list element establishes an inline-size query container. The product card changes its layout when the relevant container reaches the specified width.
The container must be an appropriate ancestor of the element being queried. Test the component in different parent layouts to verify the intended behavior.What determines when an inline-size container query rule becomes active?