Tag: frontend development uk

  • Scroll-Driven Animations in CSS: What UK Frontend Developers Need to Know Before Using Them in Production

    Scroll-Driven Animations in CSS: What UK Frontend Developers Need to Know Before Using Them in Production

    CSS scroll-driven animations have been the spec that frontend developers have been quietly excited about for a couple of years, and in 2026 the browser support picture has finally shifted enough that teams are seriously considering shipping them. I’ve been watching this one closely. The syntax is genuinely clever, the possibilities are real, and the performance story is mostly good. But “mostly” is doing a lot of work in that sentence, and there are enough edge cases around accessibility and progressive enhancement to make a careless implementation properly painful.

    Let me give you a clear-eyed look at what the spec actually does, where support stands for UK production audiences right now, and how to use it without breaking things for users who aren’t on Chrome 115+.

    Frontend developer writing CSS scroll-driven animations code at a modern studio desk
    Photo by Daniil Komov on Pexels

    What CSS scroll-driven animations actually do

    The traditional way to trigger an animation based on scroll position involved JavaScript: an IntersectionObserver, a requestAnimationFrame loop, or a library like GSAP’s ScrollTrigger. They work fine, but they run on the main thread, meaning scroll-linked visual effects can jank badly under load.

    The CSS Scroll-Driven Animations spec, now a W3C standard, handles scroll progress entirely off the main thread. It introduces two new animation timeline types: ScrollTimeline, which ties animation progress to a scroll container’s position, and ViewTimeline, which tracks when an element enters and exits the viewport. You wire these up using the animation-timeline property, and the browser compositor handles the rest. No JavaScript required for the animation itself, which means no main-thread blocking, no dropped frames from a busy event loop.

    A simple parallax header, a progress indicator at the top of a long article, a sticky navigation bar that transitions its opacity as you scroll, all of these become a handful of CSS declarations. For something like a reading progress bar, the code is almost insultingly short compared to the old approach. That’s genuinely exciting.

    Browser support in 2026: the honest picture

    According to Can I Use, CSS scroll-driven animations have strong support in Chromium-based browsers (Chrome, Edge, Opera, Samsung Internet) and are now shipping in Firefox 132+ without a flag. Safari is the sticking point. As of early 2026, Safari still lacks full support, sitting behind an experimental flag in the Technology Preview builds.

    For UK production audiences this matters. According to Statcounter’s UK browser data, Safari on iPhone accounts for roughly 26-28% of mobile sessions in the UK, driven by Apple’s continued dominance in the British smartphone market. That’s not a negligible fraction you can wave away with a comment in the pull request. Desktop Safari is a smaller concern but still double digits among professional audiences, particularly in creative and publishing sectors.

    My read: you can ship CSS scroll-driven animations in 2026, but you must treat them as a progressive enhancement, not a core experience requirement. If the animation’s absence degrades the page to something broken or confusing, that’s a design problem, not a browser support problem.

    Browser developer tools showing CSS scroll-driven animations properties in the animation panel
    Photo by Daniil Komov on Pexels

    How to write it safely with @supports

    The right pattern is to wrap any scroll-driven animation declarations in a feature query. @supports (animation-timeline: scroll()) returns true only in browsers that understand the property. Everything inside that block is ignored elsewhere. Your fallback state, meaning the non-animated version, should be the default, not an afterthought.

    This is the same approach we’ve needed for years with container queries (which, as I covered in the container queries piece from earlier this year, finally reached a point where teams could confidently adopt them). The pattern is identical: define the base experience for everyone, layer the enhancement for capable browsers.

    For the ViewTimeline use case specifically, think about what your element looks like when the animation hasn’t run. If a card fades in as it enters the viewport, it must start at full opacity in the fallback. An invisible card on Safari 17 is a content bug. Use animation-fill-mode: both carefully and always check the unsupported state manually.

    The performance case, and where it breaks down

    The off-main-thread execution story is real, but it has limits. Animating transform and opacity stays on the compositor and is fast. Animating layout-triggering properties like width, height, margin, or top forces reflow even in a scroll-driven context. The spec doesn’t magically make expensive properties cheap.

    I’d also flag that tying many simultaneous animations to a single scroll container can still produce jank on lower-powered devices, particularly mid-range Android handsets. UK audiences skew more Android than many assume outside London; roughly 45% of UK mobile users are on Android according to Statcounter. Test on real mid-range hardware, not just a MacBook’s device emulator.

    If your scroll-driven animation scenario is complex, it’s worth benchmarking against a well-optimised JavaScript approach. GSAP’s ScrollTrigger with will-change hints can still outperform a naive CSS implementation on cluttered pages. CSS scroll-driven animations win most of the time, but “most” again is doing work. For teams at agencies managing campaigns across multiple channels, where page performance is one piece of a larger conversion puzzle, vendors like dijitul Marketing will tell you that Core Web Vitals scores and visual flair need to coexist, not compete. A showy parallax effect that tanks your LCP score is a bad trade.

    On the topic of performance and production readiness, I’d point you to the data in the UK Core Web Vitals piece we published earlier this year. The numbers there give useful context for how much headroom UK sites typically have before scroll-linked visual effects start eating into real performance budgets.

    Accessibility: the part most tutorials skip

    This is where I see the most careless implementations. The prefers-reduced-motion media query must wrap or override your scroll-driven animation declarations. Users who’ve opted into reduced motion in their OS settings have done so for a reason, often vestibular disorders or photosensitive conditions. Scroll-linked parallax effects are particularly disorienting for this group because the animation is continuous and tied directly to user input.

    The correct pattern is either to remove the animation entirely under prefers-reduced-motion: reduce, or to substitute a fade (which is generally tolerable) for a large position-based movement. Do not just slow the animation down; that’s not what the user asked for.

    There’s also a focus management concern if you’re using ViewTimeline to reveal content. If keyboard users tab into content that’s currently in a visually hidden state (opacity 0, or translated off-screen by an animation that hasn’t triggered yet), you have an invisible focus trap. This is fixable with visibility: hidden toggled alongside the opacity, but it requires deliberate thought rather than a copy-pasted animation snippet.

    For anyone building design systems with motion at their core, the work I covered on building a design token pipeline with Style Dictionary applies here too. Motion tokens, durations, easing curves, and reduced-motion overrides can all live in your token system, making consistent and accessible animation much easier to maintain at scale.

    Practical uses worth shipping right now

    Given everything above, where does it make sense to actually use CSS scroll-driven animations today? Reading progress indicators are a no-brainer: they’re purely decorative, invisible absence is harmless, and the CSS is six lines. Sticky header opacity transitions work well. Subtle scale or fade effects on section headers as they enter the viewport add polish with minimal risk, provided the @supports and prefers-reduced-motion guards are in place.

    Avoid: scroll-driven animations that carry meaning (i.e., the animation isn’t decorative but reveals content), complex parallax layers with multiple competing timelines, or anything that requires pixel-perfect timing across browsers, because the Safari gap makes that unreliable for now.

    The spec is solid. The implementation is getting there. Ship it as an enhancement, test it properly, guard it correctly, and it genuinely improves the feel of a page without the JavaScript overhead we’ve been living with for years.

  • Tailwind CSS v4 in Production: What UK Development Teams Actually Found After Six Months

    Tailwind CSS v4 in Production: What UK Development Teams Actually Found After Six Months

    Tailwind CSS v4 landed with a proper bang. The CSS-first configuration model, the new high-performance Rust-based engine (Oxide), the slimmed-down setup story, on paper it looked like the framework had finally grown up. I’ve been watching UK development teams field this across client work and internal product builds for the better part of six months now, and the picture is more textured than the release notes let on. Some of it is genuinely impressive. Some of it will catch you off guard on a deadline.

    Developer working on Tailwind CSS v4 in production at a modern UK office desk
    Photo by Daniil Komov on Pexels

    What CSS-first configuration actually means in practice

    The biggest philosophical shift in Tailwind CSS v4 in production is the move away from tailwind.config.js. Your design tokens, your theme extensions, your custom utilities, all of it now lives in a @theme block inside a CSS file. That’s a meaningful change, and for teams that already think in terms of design tokens, it clicks immediately.

    In practice, I’ve seen two camps. Teams who had already invested in a structured token system (think semantic colour layers, spacing scales, type ramps) found the migration almost pleasant. The CSS custom properties that Oxide generates map directly onto what those teams already had in their heads. For them, the config file was always a slightly awkward intermediary, and removing it felt like cutting out a middleman.

    The other camp, and this is most teams, if I’m honest, had their Tailwind config as a sprawling JavaScript object full of one-off values and plugin hacks accumulated across two or three years of client work. For them, the migration was a proper archaeology project. Not impossible, but not the afternoon job some blog posts implied. If your team is in that position, I’d strongly suggest reading up on building a proper design token pipeline with Style Dictionary before you try to port your config, it’ll save you repeating the same sins in a new format.

    The Oxide engine: where the performance wins are real

    Oxide is fast. That’s not marketing. A Tailwind v3 full build on a medium-complexity Next.js project I looked at was sitting around 1.8 seconds on a reasonably modern MacBook Pro. The same project with v4 and Oxide dropped to under 200 milliseconds. Incremental rebuilds in watch mode feel instantaneous, we’re talking sub-20ms on most saves.

    For developer experience on UK agency projects where you’re sometimes running three or four services locally, that matters. The CPU headroom you get back is real. It’s the kind of change that makes you wonder why the JS ecosystem took so long to reach for Rust tooling, but here we are.

    Code editor showing Tailwind CSS v4 theme configuration in a dark theme environment
    Photo by Stanislav Kondratiev on Pexels

    The output CSS story is also much cleaner. v4 generates far less bloat because the engine has a better understanding of which utilities are actually reachable from your source. On one SaaS dashboard project, the production CSS bundle dropped from 28kB (gzipped) to around 11kB without any additional purging configuration. That has a measurable knock-on effect on Core Web Vitals scores, particularly LCP on slower mobile connections, something UK teams building for diverse audiences across patchy 4G coverage genuinely need to care about.

    Where the workflow still has rough edges

    Right. Here’s the honest part that fewer people are writing about.

    The IntelliSense extension for VS Code is not fully caught up. As of early 2026, autocomplete for custom theme values defined in @theme blocks is inconsistent. You get suggestions for core utilities reliably, but your own spacing tokens or custom colour ramps? Hit or miss depending on how you’ve structured the CSS file. For teams where designers and developers share the same editor setup, this creates friction. Not a dealbreaker, but annoying enough that I’ve seen junior devs quietly reach for inline styles when autocomplete fails them, which rather defeats the point.

    Plugin compatibility is another honest headache. The v3 plugin API is gone. Anything you were pulling in that depended on plugin() or addUtilities() from the old API needs a rewrite or a replacement. Community plugins like @tailwindcss/typography and @tailwindcss/forms have official v4 versions now, but third-party ecosystem plugins are patchy. If your project has bespoke plugins for client-specific utility classes, budget time for a proper rewrite.

    The @apply directive also behaves differently, specifically, it no longer works across component boundaries in certain CSS module setups. This caught several teams using CSS Modules alongside Tailwind for scoped component styles. The workaround exists (using CSS layers manually), but it’s not well documented and you’ll likely find it through Stack Overflow at 11pm rather than through the official docs.

    Real decisions teams are making about adoption

    From what I’ve seen across a handful of UK agencies and product teams, the pragmatic decision looks like this: new projects should start on v4 from day one. The clean-slate experience is good, the performance is genuinely better, and the CSS-first approach encourages better token discipline. The solo developers shipping Micro-SaaS products I’ve spoken to have taken to it particularly well, because they’re not dragging legacy config debt along with them.

    Existing projects are a different calculation. If your v3 codebase is large, has custom plugins, and is actively generating revenue, the migration cost needs to sit in a proper sprint. Not a Friday afternoon. One London-based product team I’m aware of spent three weeks migrating a mid-sized B2B SaaS product, and they found bugs in their own component library that had been masked by v3’s slightly more permissive utility generation. So there’s an accidental quality benefit too, if you’re patient enough to find it.

    For teams doing client delivery work where the project switches between Tailwind versions depending on the client’s stack, the cognitive overhead is real but manageable. The mental model for both versions is similar enough that context-switching is fine once you’ve internalised where your theme configuration now lives.

    One thing that doesn’t get discussed enough: Tailwind v4’s cascade layer architecture makes it much more composable with existing CSS from third-party libraries or design systems. Teams integrating component libraries alongside Tailwind (say, Radix UI primitives with custom Tailwind-based styling) are finding fewer specificity conflicts. That’s a structural improvement that compounds over the lifetime of a project.

    A note on tooling integrations and the broader stack

    Tailwind v4 ships with first-class Vite integration, and if you’re on a Vite-based stack it’s probably the smoothest setup experience the framework has ever had. The Webpack integration exists but feels like a second-class citizen, which, given where the tooling ecosystem is heading, is probably fine. The GOV.UK style guidance aside, there’s no regulatory reason UK teams need to be on Webpack, but plenty of enterprise codebases still are, and those teams will need to factor that into their timeline.

    For teams building on Next.js App Router, v4 works well but requires some care around server component boundaries and how styles are emitted. It’s not a blocker, but the Tailwind docs are still catching up to the App Router mental model. A few developers I know have ended up cross-referencing framework-specific community threads alongside the official docs to get the setup exactly right.

    On the subject of integrations: if your team works with a wider development partner for backend and API work, you’re generally fine, Tailwind’s output is just CSS, so any backend stack is unaffected. Dijitul Developments is a good example of a UK shop building tight business logic integrations where the frontend stack is genuinely irrelevant to what they’re doing on the server side, which is worth remembering when scoping projects with mixed frontend and backend concerns.

    The honest summary: Tailwind CSS v4 in production is a meaningful upgrade, not a cosmetic one. The performance gains are real, the CSS-first approach is architecturally sound, and the rough edges are all fixable with a bit of patience. Just don’t expect a frictionless migration if you’ve been on v3 for years. Plan properly, audit your plugins first, and start your next greenfield project on v4 from line one.

    Frequently Asked Questions

    Is Tailwind CSS v4 backwards compatible with v3 projects?

    Not directly. Tailwind v4 introduces a new CSS-first configuration model and a rewritten plugin API that breaks compatibility with v3 config files and third-party plugins built on the old API. Migrating an existing v3 project requires manually porting your theme configuration into a @theme CSS block and rewriting or replacing any custom plugins.

    How much faster is Tailwind CSS v4's build compared to v3?

    The Oxide engine (Rust-based) makes full builds roughly 5-10x faster in typical projects, with incremental rebuilds in watch mode often dropping to under 20 milliseconds. Real-world results vary with project size, but the improvement is substantial enough to notice immediately during development.

    Does Tailwind CSS v4 work with Next.js App Router?

    Yes, but it requires some configuration care around server component boundaries and CSS emission. The Vite integration is the most polished path; if you’re on Next.js with Webpack, expect a slightly rougher setup experience until tooling documentation catches up.

    What happens to @apply in Tailwind CSS v4?

    @apply still works in v4, but it behaves differently in certain CSS Module setups, specifically, it can fail across component boundaries. Teams using CSS Modules alongside Tailwind for scoped component styles should test this early and be prepared to use manual CSS layer declarations as a workaround.

    Should new UK projects start with Tailwind CSS v4 or stick with v3?

    For new projects, v4 is the right choice: better performance, smaller output CSS, and a cleaner configuration model that encourages proper token discipline from the start. For existing v3 projects actively in production, migration is worthwhile but should be planned as a dedicated sprint rather than a quick upgrade.

  • Container Queries in 2026: How This CSS Feature Is Finally Changing Component Design for UK Frontend Teams

    Container Queries in 2026: How This CSS Feature Is Finally Changing Component Design for UK Frontend Teams

    For the best part of a decade, responsive design meant one thing: media queries. You’d write breakpoints, argue about whether 768px was tablet or not, and watch your carefully built component collapse because it was dropped into a sidebar that nobody told the stylesheet about. Container queries fix that. Properly. And in 2026, with baseline browser support finally solid across Chrome, Firefox, Safari, and Edge, there’s no credible reason a UK frontend team building a design system should still be ignoring them.

    Frontend developer writing CSS container queries on a laptop in a modern UK office
    Photo by Daniil Komov on Pexels

    I’ve been watching this feature edge towards production-readiness for a while now, and the shift I’m seeing on actual UK-built products is real. Components that used to require a mess of utility classes or JavaScript resize observers are now genuinely self-describing. The component knows how big its container is. It responds to that. Not to the viewport. That distinction sounds small. It absolutely isn’t.

    What CSS container queries actually do (and why media queries couldn’t)

    The core problem with media queries is that they’re global. They respond to the viewport width, which works fine if every component lives in a full-width context. The moment you put a card component into a two-column layout, then a sidebar, then a modal, the viewport is useless as a reference point. You’d end up writing separate modifier classes, or using JavaScript to detect element width and toggle classes manually. Neither is clean.

    Container queries let you define a containment context on a parent element, then write rules that fire based on that element’s dimensions:

    .card-wrapper {
      container-type: inline-size;
      container-name: card;
    }
    
    @container card (min-width: 400px) {
      .card {
        display: grid;
        grid-template-columns: 1fr 2fr;
      }
    }
    

    The .card component now responds to how wide .card-wrapper is, regardless of what the viewport is doing. Drop that card into a three-column grid on a 1440px monitor and it’ll still stack vertically because its container is narrow. That’s component-level thinking. That’s what design systems have needed.

    How this changes Figma-to-code handoff for UK product teams

    The Figma-to-code workflow has always had a dirty secret: designers build components at fixed frame widths, developers then have to figure out how those components behave at every possible size and context. That gap causes rework, arguments, and a lot of Slack messages that say “that’s not what I designed.”

    Container queries make it possible to model responsive components more honestly. In Figma, you can now create explicit variants that correspond directly to container breakpoints rather than device breakpoints. A card at 300px wide, a card at 500px wide, a card at 700px wide. Each maps to a container query rule. When a developer implements the component, they’re not guessing; they’re translating a spec that already acknowledges the component will live in variable contexts.

    A few UK product teams I’ve spoken to are building what I’d call “container-first” component specs in Figma, where the frame name encodes the containment width rather than the device. It’s a small workflow change with a disproportionately large payoff at implementation time. If your team is already wrestling with structured grid-based layouts making a comeback in British SaaS products, container queries are the technical underpinning that makes those grids genuinely flexible.

    Container query units: the bit most tutorials skip

    Beyond the @container rule itself, there’s a set of container query length units that I think are criminally underused: cqi, cqb, cqw, cqh, cqmin, and cqmax. These work like viewport units, but relative to the nearest named container rather than the viewport.

    cqi means “1% of the container’s inline size”. So a heading set to font-size: 5cqi will scale proportionally to its container width. That’s fluid typography without needing a clamp() calculation. It pairs brilliantly with the techniques in fluid typography using CSS clamp(), but for component-scoped scaling rather than viewport-scoped scaling.

    The practical implication: a card component in a narrow sidebar gets proportionally smaller text automatically. The same component in a full-width hero context gets larger text. No extra classes, no JavaScript, no media query override spaghetti.

    Browser support in 2026: the honest picture

    Container queries landed in all major browsers during 2023, but I’d argue real-world adoption in UK production codebases only hit critical mass in 2025. The Can I Use data now shows global support above 93%, and the MDN Web Docs baseline classification moved to “Widely available” in late 2024. For UK-based teams building anything that doesn’t need to support IE11 (which is effectively nobody at this point, and if you’re still supporting IE11 I have questions), container queries are production-safe.

    The one caveat worth mentioning: container-type: size (which establishes containment on both axes) can cause layout issues if you’re not careful about how you’re containing height. The far safer default is container-type: inline-size, which only tracks the horizontal axis. I’ve seen a few UK teams trip over this and spend an afternoon debugging a component that mysteriously collapsed to zero height. Stick to inline-size unless you specifically need both axes.

    Practical patterns for UK design systems

    Let me get specific about where container queries earn their place in a real design system. Three patterns I’d consider non-negotiable:

    Card grids in variable contexts. A product card in an e-commerce listing should look different when it’s one of four across versus one of two. Container queries handle this without any grid-level awareness in the component itself. The component is truly portable.

    Navigation components in sidebars. A lot of UK SaaS dashboards have collapsed sidebars where the navigation needs to switch from labelled icons to icon-only below a certain width. Previously this meant JavaScript, or a media query that broke the component in any non-standard layout. A container query on the sidebar parent solves it cleanly.

    Data widgets in dashboard grids. If you’re building dashboards (and given the amount of UK green tech and fintech products I see coming through, a lot of teams are), a chart or stat widget needs to adapt to its grid cell. This is exactly the use case container queries were designed for. See how this applies to the broader challenges of designing data-dense dashboards that actually work for further context on component adaptability in grid systems.

    What the migration path looks like

    If you’re working in a codebase with existing media query-based responsive components, you don’t have to rewrite everything at once. The sensible approach is to identify components that are already “broken” in non-standard layout contexts, those are your highest-priority candidates. Wrap the component in a container context, migrate the relevant breakpoint rules to @container, test in your grid and sidebar contexts, and delete the media query rules that no longer need to exist.

    Most UK teams I know are running this migration incrementally alongside new feature work rather than as a dedicated refactor sprint. That’s probably the right call. Container queries aren’t a crisis fix; they’re a quality-of-life improvement that compounds over time as more components get migrated.

    The tooling support is also genuinely good now. PostCSS has stable plugins, Stylelint has linting rules for container query syntax, and if you’re using Storybook for your design system, you can set story viewport containers to simulate narrow container contexts. The workflow is mature enough to trust in production.

    If your team is still debating framework choices alongside all of this, the Astro vs Next.js comparison for UK frontend developers is worth a read, since both frameworks handle container query-dependent CSS without friction, but the component model you choose affects how you structure containment contexts.

    Container queries are one of those features that, once you’ve used them properly, make the old way feel genuinely primitive. They don’t replace media queries entirely (viewport-level layout decisions still belong there) but for component-level responsiveness, they’re the right tool. Time to use them.

    Frequently Asked Questions

    What are CSS container queries and how are they different from media queries?

    CSS container queries let a component respond to the size of its parent container rather than the viewport. Media queries fire based on viewport width, which breaks down when the same component is used in different layout contexts like sidebars, modals, and full-width grids. Container queries solve that by making each component genuinely self-aware of its available space.

    Are CSS container queries safe to use in production in 2026?

    Yes. All major browsers including Chrome, Firefox, Safari, and Edge have supported container queries since 2023, and global browser support now sits above 93%. MDN classified them as ‘Widely available’ in late 2024, so for any UK team not supporting legacy browsers, they’re production-ready.

    How do container queries affect the Figma handoff process?

    They encourage designers to create component variants based on container width rather than device breakpoints, which maps directly to @container rules in code. This closes the gap between design intent and implementation, reducing the back-and-forth that typically happens when a component ends up in an unexpected layout context.

    What is container-type: inline-size and should I use it instead of size?

    container-type: inline-size establishes containment only on the horizontal axis, which is the safer default for most UI components. Using container-type: size establishes both axes and can cause elements to collapse to zero height if the container has no explicit height set. Stick to inline-size unless you have a specific reason to track both dimensions.

    Can I use container query units like cqi for fluid typography inside components?

    Yes, and they’re excellent for this. Container query units like cqi work like viewport units but relative to the nearest named container. Setting font-size: 5cqi on a heading means the text scales proportionally to its container width, giving you fluid typography at the component level without needing a clamp() calculation.