The phrase “Animating layouts without JavaScript” isn’t just a neat trick — it’s a modern approach to building faster, more accessible UI by leveraging CSS features like @property, container queries, and the emerging Houdini APIs. This article walks through practical patterns you can drop into real sites: accessible toggles (accordions, drawers, responsive cards), performant layout transitions, and graceful fallbacks for browsers that don’t support every feature yet.
Why choose CSS-first layout animation?
Moving layout toggles from JavaScript to CSS reduces runtime overhead, simplifies maintenance, and often improves perceived performance because the browser can optimize paint and compositing. CSS-driven animations also pair nicely with semantic HTML (details/summary, inputs) and user preferences (prefers-reduced-motion), improving accessibility out of the box.
Core tools and when to use them
- @property — register a custom property with a type so the browser can smoothly interpolate it; perfect for animating numeric CSS variables.
- Container Queries — let components react to their container size instead of the viewport, enabling context-aware animations and layout shifts without JS.
- Houdini (Paint/Layout/Animation Worklets) — powerful low-level APIs to implement custom layout, paint, and animation logic with better performance and native-like control.
Practical patterns
1. Accessible accordion using details + @property
details/summary is semantic and keyboard friendly. The classic problem: animating height when content size is dynamic. A robust CSS-first approach uses a numeric custom property declared with @property, toggled via the details open attribute, and then used to interpolate a transform or max-height.
/* register an animatable custom property */
@property --open {
syntax: '<number>';
inherits: false;
initial-value: 0;
}
details[open] { --open: 1; }
details { --open: 0; }
.details__content {
overflow: hidden;
transition: max-height 280ms ease, opacity 220ms ease;
/* fallback uses max-height (safe) */
max-height: calc(var(--open) * 800px); /* safe upper-bound fallback */
opacity: calc(var(--open));
}
Notes: use a reasonable max-height fallback for browsers without full @property support; prefer transform (scaleY) when you can determine the origin and visual effect is acceptable. Always respect prefers-reduced-motion and provide instant open/close for that case.
2. Responsive layout switches with container queries
Container queries let components adapt their layout and animate between configurations when their container changes size — ideal for card grids, nav bars, and embedded widgets that need to slide or reflow without global media queries.
/* Example: change a card from stacked to side-by-side and animate properties */
.card {
container-type: inline-size;
transition: transform 320ms ease, box-shadow 240ms ease;
}
/* When the card is wide enough, shift to a different layout */
@container (min-width: 420px) {
.card { display: grid; grid-template-columns: 1fr 2fr; transform: translateY(-6px); }
}
Because container queries are scoped to the component, they make it easier to animate local layout changes without worrying about global breakpoints or JS-driven resize observers.
3. Smooth reflow and masonry with Houdini Layout Worklet
Want to animate a masonry rearrangement or custom flow without the CPU overhead of JavaScript layout calculations? Houdini’s Layout Worklet allows you to author a custom layout algorithm that the browser can execute in worker-like contexts and that integrates with layout passes.
- Create a layout worklet that positions children and exposes a small set of CSS custom properties (e.g., –layout-gap, –row-height).
- Transition between layout states by changing those custom properties; the browser can run the worklet and produce smooth, GPU-friendly transitions.
Important: Houdini is powerful but still vendor-dependent; ship it as progressive enhancement with CSS-only fallbacks (grid/flexbox) for compatibility.
Accessibility and performance best practices
- Always prefer semantic controls (details, button, input[type=”checkbox”]) so assistive tech understands interactions.
- Respect prefers-reduced-motion: set transitions to none when users request reduced motion.
- Use hardware-accelerated properties (transform, opacity) when possible; animate layout properties only when necessary and avoid expensive paints on large elements.
- Provide fallbacks: if @property, container queries, or Houdini APIs aren’t available, ensure toggles still function (instant open/close) and layout degrades gracefully.
Real-world recipe: drawer toggle without JS
Combine a visually-hidden checkbox, CSS custom property, and transform to create a performant drawer that is keyboard-accessible and respects reduced motion.
/* HTML: <input id="nav-toggle" type="checkbox"> <label for="nav-toggle">Menu</label> */
/* CSS */
@property --drawer {
syntax: '<number>';
inherits: false;
initial-value: 0;
}
#nav-toggle { position: absolute; left: -9999px; }
#nav-toggle:checked ~ .drawer { --drawer: 1; }
.drawer {
transform: translateX(calc(-100% + (var(--drawer) * 100%)));
transition: transform 360ms cubic-bezier(.2,.9,.2,1);
}
@media (prefers-reduced-motion: reduce) { .drawer { transition: none; } }
This pattern keeps logic in CSS and HTML while maintaining the expected keyboard and focus behavior because the checkbox is focusable and tied to the label trigger.
When to still use JavaScript
CSS-first approaches are excellent for toggles, layout shifts, and decorative transitions. JavaScript remains appropriate when you need complex state management, orchestration across multiple components, or when you must measure content dynamically and there is no reasonable CSS-only fallback. When using JS, prefer mixing progressive enhancement with the CSS techniques above rather than full reliance on imperative animations.
Animating layouts without JavaScript is about choosing the right tool for the job: @property makes custom properties animatable, container queries enable component-scoped responsive transitions, and Houdini unlocks custom layout and paint capabilities — all while keeping markup semantic and interactions accessible.
Conclusion: Embrace CSS-first toggles where possible to reduce complexity, improve performance, and increase accessibility; combine @property, container queries, and Houdini progressively and always provide sensible fallbacks.
Want example snippets or a small starter repo tailored to your project? Get in touch and I’ll prepare a ready-to-drop-in demo.
