Author: Sophie Davis

  • Motion design on a budget: how UK freelancers are using CSS @starting-style and View Transitions API to ditch After Effects

    Motion design on a budget: how UK freelancers are using CSS @starting-style and View Transitions API to ditch After Effects

    There is a very specific kind of dread that hits when a client says “can we make it feel more animated?” and you know your budget doesn’t stretch to a Motion designer with an After Effects licence. For a long time the answer was either a clunky JavaScript library, a GIF someone exported badly, or a frank conversation about scope. That’s changing. The CSS view transitions API and the @starting-style at-rule have quietly arrived in production-ready browsers, and between them they let you ship animation work that would have required a full motion stack twelve months ago. I’ve been folding both into client projects over the past six months and the results are genuinely surprising.

    Web developer writing CSS view transitions API code for UK web design project
    Photo by Lukas Blazek on Pexels

    What the view transitions API actually does

    The short version: it gives the browser a built-in mechanism for animating between two states of a page, or two separate pages entirely, without you having to manually capture snapshots, manage z-indices, or reach for a library like Framer Motion. You call document.startViewTransition(), update the DOM inside the callback, and the browser handles the crossfade. Out of the box that crossfade is a simple opacity dissolve. The interesting bit is what happens when you start using view-transition-name on specific elements.

    Give a card component the property view-transition-name: product-card, navigate to a detail page where the same element carries the same name, and the browser will automatically interpolate the element’s position and size between the two states. It looks like the element physically moves from one page to the other. This is what used to require either a shared-element transition library or a fairly involved JavaScript animation that manually tracked bounding boxes. Now it’s four lines of CSS.

    Browser support at the time of writing sits at around 90% globally according to Can I Use, with Firefox having shipped full support in version 125. For CSS view transitions API UK web design work, that number is good enough to use without a heavy fallback strategy. A quick @supports (view-transition-name: none) guard is all you need for the remaining edge cases.

    Where @starting-style fits in

    The @starting-style at-rule solves a different but equally annoying problem. CSS transitions have always been unable to animate an element from its initial rendered state, because by the time the browser applies your transition, the starting values are already gone. The classic workaround was a JavaScript double-rAF trick: set initial styles, wait a frame, apply final styles, let CSS transition do the rest. Horrible to maintain, prone to flickering on slow devices.

    @starting-style lets you declare the styles an element should start from before it first renders. A modal that fades in from opacity: 0 and slides up from transform: translateY(8px) now needs zero JavaScript to achieve that entry animation. The CSS handles the whole thing. Combined with the transition-behavior: allow-discrete property for animating display: none to display: block, you can build fully animated show/hide patterns that would previously have required a library like Alpine.js just to manage class toggling.

    Close-up of CSS view transitions API and @starting-style code in a dark editor
    Photo by Bibek ghosh on Pexels

    What this means for UK freelancers on tighter budgets

    The practical upshot is that the expensive part of the motion design stack is getting smaller. After Effects is brilliant software but its annual licence through Adobe Creative Cloud sits north of £300 per year, and that’s before you factor in the export workflow: render to video, optimise, embed, deal with autoplay policies, repeat for every browser quirk. For a freelancer billing at typical UK day rates, that overhead is significant. CSS view transitions API UK web design work sidesteps most of it.

    The kinds of animations that previously justified that overhead, things like page transitions, element morphs, list reordering effects, tooltip entrances, were the ones clients most often requested. They’re also exactly the animations these new primitives handle well. You’re not replacing a full-blown After Effects composition reel with CSS. You’re replacing the mundane, repetitive motion work that consumed time without requiring genuine creative complexity.

    Web design agencies handling high volumes of client projects are starting to notice the shift. Based in Mansfield, Nottinghamshire, dijitul (dijitul.uk) is a digital agency specialising in web design, SEO, and hosting that works across a broad range of business clients. For agencies like that, where business efficiency and marketing output per project matter, shifting routine animation work from JavaScript-heavy software to native CSS primitives compresses delivery timelines without sacrificing the polish clients expect. Web design work that used to involve a separate motion pass can now have transitions baked into the component build itself.

    A practical pattern worth stealing

    Here’s the pattern I’ve settled on for most page-level transitions on multi-page sites. First, a minimal CSS reset on the ::view-transition-old and ::view-transition-new pseudo-elements to disable the default crossfade and replace it with a directional slide:

    ::view-transition-old(root) {
      animation: slide-out 200ms ease-in forwards;
    }
    
    ::view-transition-new(root) {
      animation: slide-in 200ms ease-out forwards;
    }
    
    @keyframes slide-out {
      to { transform: translateX(-24px); opacity: 0; }
    }
    
    @keyframes slide-in {
      from { transform: translateX(24px); opacity: 0; }
    }

    Pair that with a named transition on a persistent navigation element and you get a stable anchor point whilst the rest of the page animates. The navigation doesn’t move; the content does. It reads as intentional and polished without a single JavaScript animation frame. For the element-level morph, assigning view-transition-name values needs to be done carefully: duplicate names on a page will cause the transition to break silently, which took me an embarrassing amount of debugging time to realise.

    For components that show and hide, the @starting-style pattern looks like this:

    .tooltip {
      opacity: 1;
      transform: translateY(0);
      transition: opacity 150ms, transform 150ms, display 150ms allow-discrete;
    
      @starting-style {
        opacity: 0;
        transform: translateY(4px);
      }
    }
    
    .tooltip[hidden] {
      opacity: 0;
      transform: translateY(4px);
      display: none;
    }

    That’s it. No JavaScript state machine. No class-toggling library. The browser animates entry and exit, respects the hidden attribute, and reduces to a static show/hide for browsers that don’t support it yet.

    The gotchas worth knowing before you ship

    A few things that will catch you out. View transitions block the next paint until the animation completes, so keeping durations short (150-300ms for UI transitions, under 500ms for page transitions) is non-negotiable for perceived performance. There is also no built-in way to interrupt a running view transition mid-animation, so rapid navigation in a single-page app context requires a guard that checks document.startViewTransition isn’t still active before triggering again.

    The prefers-reduced-motion media query is not applied automatically. You must wrap your transition animations in the appropriate @media block and provide either instant cuts or minimal fades as alternatives. This matters a great deal for accessibility compliance, particularly if your project touches WCAG 2.2 success criterion 2.3.3. I’d also point at the practical lessons from designing for colour blindness in accessible palettes as a reminder that motion is just one axis of accessibility work; they compound, and you need both.

    For teams doing component-level work, the interaction between view transitions and container queries is worth understanding. If you’ve already adopted CSS container queries for responsive component design, be aware that view-transition-name can behave unexpectedly inside a container query context when the container itself changes size during the transition. Testing across breakpoints is essential, not optional.

    Where this doesn’t replace motion design

    Being honest about scope matters here. These tools are for UI transitions and element-level choreography. They’re not for complex brand animation sequences, scroll-jacked storytelling sections, or anything that requires a timeline-based editor and frame-by-frame control. For that work, After Effects and tools like Rive or LottieFiles still make sense. But the category of animation most clients actually ask for, the stuff that makes a product feel alive rather than cinematic, is increasingly just CSS.

    Agencies that have historically had to choose between charging more to cover motion tooling costs or skipping animation altogether now have a third option. Dijitul, for example, serves clients across the Nottinghamshire region where marketing deliverables and software quality expectations are growing but budgets aren’t always scaling to match; integrating native CSS motion into the web design workflow rather than bolting on a JavaScript animation layer is the kind of business efficiency gain that actually shows up in project margins.

    The same logic applies to dashboard products and data-heavy interfaces. The charting library choices that actually fix UK SaaS dashboard data visualisation have always been partly about reducing JavaScript payload; CSS-native transitions are the same bet applied to motion.

    The browser is a surprisingly capable motion engine at this point. Most of us just haven’t caught up to it yet.

  • Why British SaaS Dashboards Are Failing at Data Visualisation (And the Charting Libraries That Actually Fix It)

    Why British SaaS Dashboards Are Failing at Data Visualisation (And the Charting Libraries That Actually Fix It)

    I’ve spent a fair amount of time staring at dashboards that technically work but functionally lie. Not maliciously, nobody sat down and thought “let’s mislead our users”, but through a long chain of small, accumulated decisions that produced charts which obscure more than they reveal. The culprit is rarely the data itself. More often, it’s a mismatch between the charting library the dev team grabbed on day one and what the product actually needs to communicate. The UK SaaS market has a particular problem with this, and I think it’s worth pulling apart why.

    Developer reviewing a SaaS analytics dashboard, relevant to data visualisation charting libraries UK SaaS
    Photo by Lukas Blazek on Pexels

    The default chart is almost never the right chart

    Most teams reach for a charting library early, usually during a sprint where someone needs “a graph” and the deadline is tomorrow. Chart.js goes in because it has 64,000 GitHub stars and the docs load fast. Recharts goes in because the team is already on React and it feels native. Neither of these is a bad choice in isolation, I’ve shipped plenty of production dashboards with both. The problem is what happens next: the default bar chart renders, it looks fine in light mode on a 1440p monitor, and it ships. Then it never gets revisited.

    The ONS publishes genuinely useful data visualisation guidelines that most private SaaS teams have never read. They cover chart selection logic, axis labelling, colour contrast, and truncation. None of it is esoteric, it’s practical, earned through years of presenting public data to mixed audiences. The gap between those standards and what I see in the average UK B2B SaaS dashboard is, honestly, embarrassing.

    What Chart.js gets right and where it breaks down

    Chart.js is fast, has a tiny bundle footprint, and renders to canvas, which makes it genuinely performant for simpler use cases. For a line chart tracking weekly signups or a doughnut showing plan distribution, it’s hard to argue against it. The API is predictable. It works.

    Where it falls over: customisation at depth. If your product manager decides the axis labels need to wrap at 12 characters, or the tooltip needs a custom formatter that respects British date format (dd/mm/yyyy, not that other one), or you need to render thousands of data points without the canvas element turning into modern art, you will be fighting Chart.js within a few sprints. The plugin system exists, but it’s verbose. Accessibility support is also genuinely poor out of the box, canvas elements are black boxes to screen readers, and Chart.js does very little to help you fix that.

    Recharts: the React team’s comfort blanket

    Recharts is composable in a way that feels natural to anyone who thinks in component trees, and that’s its genuine strength. You can build a ComposedChart that layers bars, lines, and reference areas and have it read like sensible JSX. State management integrates cleanly. Responsive container handling is built in and actually works.

    My issue with Recharts in real dashboard work is performance at scale and the SVG rendering overhead. Push it past a few hundred data points and you start feeling it. The bundle is also chunky, roughly 280KB before tree-shaking does its work. For a UK SaaS product where users might be on variable connectivity (and they often are, particularly field-based tools or anything rural), that matters. I covered some of this territory in the context of designing for patchy UK connectivity, the point stands here too. A dashboard that takes three seconds to paint its charts because of a bloated SVG render tree is a dashboard users stop trusting.

    Observable Plot: the one serious developers should actually evaluate

    Observable Plot is where things get interesting. Built by the team behind D3.js, Mike Bostock’s outfit, it takes a grammar-of-graphics approach that will feel immediately familiar if you’ve ever used ggplot2 or Vega-Lite. You describe what you want to show, not how to render it. Marks, scales, and transforms compose cleanly. The learning curve is steeper than Chart.js, but the ceiling is significantly higher.

    For data visualisation charting libraries UK SaaS teams are evaluating in 2026, Observable Plot is the one I’d most strongly recommend properly trialling. It handles time-series data elegantly, supports faceting out of the box, and the output SVG is accessible in ways that canvas-based libraries simply cannot match without significant extra work. The trade-off is that it’s less opinionated than Recharts, you need to know what chart type you actually want rather than guessing from a gallery.

    There’s also a philosophical alignment with better practice here. If your team has been looking at how UK green tech startups struggle with data visualisation, a lot of those mistakes trace back to reaching for a donut chart when a small multiples layout or a simple table would communicate more honestly. Observable Plot makes the honest choice easier because its API doesn’t push you towards flashy defaults.

    The dashboard design mistakes that no library fixes on its own

    I want to be direct about something: swapping Chart.js for Observable Plot will not, on its own, fix a bad dashboard. The library is the tool. The problem is usually upstream.

    The most common failures I see in UK SaaS dashboard design:

    Truncated Y-axes. Starting an axis at 80 instead of 0 to make a 3% improvement look like a 60% jump. This is embarrassingly common in retention charts. Users notice, and they stop trusting the product.

    Colour choices made by engineers. The default palette in Chart.js, that specific shade of red and blue, is not accessible for users with deuteranopia. Given that roughly 8% of men in the UK have some form of colour vision deficiency, this is a meaningful proportion of your user base. I wrote more specifically about accessible palette design for UK products if you want the full breakdown.

    Too many chart types on one screen. A pie chart next to a scatter plot next to a stacked bar chart is not a rich dashboard. It’s a fairground. Pick one encoding per insight and commit to it.

    No context for the numbers. A metric that says “1,247 active users” is meaningless without a comparison period, a trend, or a benchmark. Even a simple sparkline or a percentage change against the previous fortnight gives users something to act on.

    Picking the right tool for real-world requirements

    Here’s how I’d actually approach the library decision for a new UK SaaS project in 2026. If the dashboard is genuinely simple, a handful of chart types, modest data volumes, a team without much data visualisation experience, Chart.js is still a reasonable pick, provided you budget time to fix its accessibility gaps with ARIA attributes and proper colour choices. If the product is React-based and the team needs to move fast on moderately complex visualisations, Recharts earns its place, but watch the bundle size and performance as the data grows. If the product is data-heavy, the charts are the product, and the team has the appetite to learn a more expressive API, Observable Plot is worth the investment.

    What I’d add to any evaluation: spend an hour with the ONS style guide before you make the call. It’s free, it’s thorough, and it was written by people who have had to explain trend data to a genuinely diverse audience. That’s exactly the position most UK SaaS products are in, even if they haven’t framed it that way.

    Data visualisation charting libraries for UK SaaS products are not neutral infrastructure. Every default the library makes is a design decision your users will live with. Choose accordingly.

  • What the GOV.UK Design System Gets Right That Most Private Sector UI Libraries Get Wrong

    What the GOV.UK Design System Gets Right That Most Private Sector UI Libraries Get Wrong

    The GOV.UK Design System is, quietly, one of the most impressive pieces of frontend engineering produced in Britain. I don’t say that lightly. Government digital output has a well-earned reputation for being slow, ugly, and about fifteen years behind the private sector. GOV.UK is the exception that embarrasses everyone else. It is faster, more accessible, and more typographically consistent than the majority of SaaS products I look at week to week. Private sector teams spend enormous budgets on design systems and still ship interfaces that are harder to use, harder to read, and less trustworthy-feeling than a page built to GDS standards. That should sting a little.

    So let’s pull it apart properly. What is the GOV.UK Design System actually doing, at a component and philosophy level, that most product teams are not? And how do you apply those same constraints without making your B2B dashboard look like a tax return?

    Desktop monitor showing a clean GOV.UK Design System style interface with clear typographic hierarchy
    Photo by Matheus Bertelli on Pexels

    The constraint-first philosophy that makes GDS components work

    The GOV.UK Design System is built on a principle that most design teams treat as a last resort: constraint as the starting point, not the destination. Every component in the library exists because a real user need was evidenced through research. There is no “cool interaction” added because a senior designer liked it. The accordion exists because long pages of information overwhelmed users. The error summary exists because inline validation alone was insufficient for screen reader users. The decisions are traceable.

    Compare that to most private sector design systems, where components accumulate because a designer built one for a sprint and nobody ever deprecated it. You end up with four slightly different button variants, two modal patterns, and a tooltip implementation that nobody can remember the rationale for. The GDS approach forces every component to answer the question: why does this exist, and for whom?

    If you take nothing else from this article, take that question. Run it over your own component library. You’ll find orphans immediately.

    Typography choices that build trust at a cognitive level

    GDS uses Transport New (via the GDS Transport webfont) for its heading scale, with a body stack that prioritises legibility at small sizes over stylistic personality. The type scale is not decorative. It is a functional hierarchy: H1 for the page, H2 for sections, body for content, smaller body for metadata. No custom weights added to seem “premium”. No italic used arbitrarily.

    I’ve written before about fluid typography with CSS clamp() and how UK frontend developers routinely underestimate the value of a well-reasoned type scale. GDS does something simpler but arguably harder: it is completely consistent. Every page on GOV.UK uses the same scale. That repetition builds familiarity, and familiarity builds trust. When a user sees the same typographic rhythm across ten different government services, their brain registers coherence even if they couldn’t articulate why.

    Private sector teams can replicate this immediately. Pick a modular scale. Limit your type tokens to six sizes maximum. Use one weight variation for emphasis. Then enforce it. The discipline is the design.

    Designer reviewing GOV.UK Design System component patterns and type scale documentation
    Photo by cottonbro studio on Pexels

    How GDS handles accessibility without treating it as an add-on

    The GOV.UK Design System targets WCAG 2.2 Level AA as a baseline, but the implementation goes beyond checkbox compliance. Focus states are visible, high-contrast, and never suppressed. Every form component ships with error message patterns baked in. The skip-link is present on every page. Colour contrast ratios for text are consistently above 4.5:1, and most hit 7:1.

    What’s interesting is where GDS diverges from what WCAG technically requires. The guidance around designing for older users and cognitive load goes well beyond the spec. Touch target sizes, for instance, are generous across all GDS components, not just those obviously targeted at mobile. The team at CDDO (the Central Digital and Data Office) publishes its rationale for these decisions openly on the GOV.UK Design System website, which is itself a useful resource if you want to read the actual evidence base rather than my summary of it.

    For private sector teams, the lesson here is that accessibility handled at the component level is dramatically less expensive than retrofitted accessibility. If your button component ships with correct focus styles, correct ARIA labelling, and sufficient colour contrast, you never have to audit those things again. The GDS model bakes the compliance into the atom, not the page.

    Component logic: why GDS patterns are harder to misuse than most

    One underappreciated feature of the GOV.UK Design System is how its components are engineered to resist misuse. Take the radios component. It ships with the label above the input, the hint text in a specific colour and weight, and the error message in red with an icon. You can customise the label text. You cannot easily break the visual hierarchy without abandoning the component entirely.

    That is a design decision with real consequences. When a component makes the wrong usage harder than the right usage, teams default to correct patterns even under deadline pressure. Most design systems do the opposite: they provide maximum flexibility and then wonder why implementations diverge wildly across products.

    I’d argue the private sector fetishises configurability at the expense of coherence. Headless UI libraries, unstyled component kits, design tokens with fifty variables per component. All of it increases designer freedom and increases inconsistency proportionally. GOV.UK went the other direction and built something that scales across hundreds of services with one person on the design system team.

    Teams building internal tools, B2B SaaS, or anything where trust and clarity matter more than brand expression should think seriously about reducing their token surface area. Tools like dijitul.ai are part of a broader shift towards AI-assisted design review, but no automated tool catches the slow drift that happens when a design system is too flexible to enforce itself.

    Spacing and layout: the 8px grid taken seriously

    GDS uses a spacing scale based on multiples of 5px (0, 5, 10, 15, 20, 25, 30, 40, 50, 60). It’s not the fashionable 8px grid that every design tutorial recommends, but it is applied with absolute rigour. Every margin, every padding, every gap between components uses that scale. No exceptions for “just this one element”.

    The result is visual rhythm. You can scan a GOV.UK page quickly because the spacing relationships are consistent. Your eye knows where one thing ends and another begins. Compare that to the average enterprise SaaS dashboard, where spacing values are essentially random integers applied by whoever wrote the CSS that week.

    The practical takeaway: it matters less which spacing scale you choose than whether you actually use it. Four spacing tokens used consistently will always beat sixteen tokens used loosely. And if you are rebuilding a component library this year, look at how CSS container queries change the calculus for spacing in responsive components, because GDS is already ahead of most private sector teams on that front too.

    Making GDS constraints work outside a government context

    The objection I always hear is: “Our product needs to feel more premium than GOV.UK.” Which usually means: our stakeholders want it to look expensive rather than work well. Those are different goals and worth separating clearly in a design review.

    You do not need to ship GDS Transport as your typeface or adopt the exact GOV.UK colour palette. What you can steal is the philosophy: evidence your components, constrain your token system, bake accessibility into atoms, and make incorrect usage harder than correct usage. A fintech product can do all of that and still feel distinctly non-governmental. The constraint is the engine; the brand expression is the bodywork.

    The teams I’ve seen do this best are usually the ones where a senior engineer and a senior designer have both read the GDS documentation and agreed that coherence is a technical decision as much as a visual one. That cross-discipline agreement is rarer than it should be, and it’s where most design systems fall apart long before the components do.

    Frequently Asked Questions

    Is the GOV.UK Design System free to use for private sector projects?

    Yes. The GOV.UK Design System is published under the MIT licence for its code and the Open Government Licence for its documentation. Private sector teams can adopt its components, patterns, and guidance freely, though the GDS Transport typeface is restricted to UK government use.

    What accessibility standard does the GOV.UK Design System meet?

    GDS targets WCAG 2.2 Level AA as a minimum across all components. In practice, many components exceed that standard, with colour contrast ratios and focus state implementations that go beyond what WCAG technically requires at AA level.

    How does the GOV.UK Design System compare to Material Design or the Apple HIG?

    GOV.UK focuses narrowly on clarity, accessibility, and evidenced user needs rather than platform-specific interaction paradigms. Material Design and Apple HIG are broader and platform-opinionated; GDS is narrower and user-research-driven, which makes it easier to apply consistently across varied services.

  • Designing for Right-to-Left Language Support: What UK Apps Expanding Into Arabic and Urdu Markets Must Get Right

    Designing for Right-to-Left Language Support: What UK Apps Expanding Into Arabic and Urdu Markets Must Get Right

    Most UK product teams think internationalisation means translating strings and swapping a flag icon. Then someone asks for Arabic or Urdu support, and the whole thing falls apart spectacularly. Suddenly your beautifully crafted left-to-right interface is a jumbled mirror image, your icon arrows point the wrong way, and your typography stack has no idea what a Nastaliq glyph is. Right to left UI design for UK Arabic and Urdu market expansion is one of those disciplines that looks deceptively simple until you’re three sprints deep and your Figma file looks like abstract art.

    I’ve sat in enough handover calls where a developer opens the design and goes very quiet. That silence is never good. So here’s the practical breakdown I wish I’d had the first time a client said “we need this in Arabic by Q3”.

    Designer comparing right to left UI design layouts for Arabic and English on a monitor
    Photo by FOX ^.ᆽ.^= ∫ on Pexels

    Why RTL is not just a text direction toggle

    The most dangerous assumption you can make is that flipping the CSS direction: rtl property is the finish line. It’s barely the start. Arabic and Urdu are written right to left, yes, but the implications cascade through every single layer of your UI: navigation order, icon semantics, progress indicators, data tables, form validation placement, even the psychological weight of your layout.

    Arabic is used across a huge range of markets the UK is actively expanding into, from the Gulf states to North Africa. Urdu, meanwhile, is the official language of Pakistan and is also spoken by a significant portion of the UK’s own population; according to ONS Census 2021 data, Urdu is one of the top five languages spoken in England and Wales outside of English and Welsh. Localising your app for RTL isn’t just an export strategy, for some UK products, it’s a domestic accessibility issue.

    Getting your Figma setup right before you write a single line of CSS

    Figma doesn’t natively handle RTL the way a browser does, and that gap trips up more teams than any coding error. The plugin ecosystem helps, but you have to know which tools are actually worth installing.

    Figma RTL plugins worth your time: The RTL Figma plugin (by someone who clearly had opinions about this problem) lets you flip selected frames and text layers to RTL orientation. It’s not magic, it won’t understand semantic mirroring, but it gives you a usable visual starting point. Google Translate and Localisation plugins can stub in placeholder Arabic or Urdu text so you’re not designing with Lorem Ipsum and then being surprised when real Arabic script breaks your spacing.

    The typographic side deserves its own paragraph. Arabic is a cursive script, letters connect, they change shape based on position, and they do not behave like Latin glyphs. Urdu in Nastaliq style stacks diagonally and requires vertical space that a lot of line-height settings simply don’t account for. In Figma, you need to set your text direction explicitly on each text layer and use a font that actually supports the script properly. Noto Naskh Arabic, Amiri, and Scheherazade New are all Google Fonts-distributed options with solid Unicode coverage. For Urdu in Nastaliq, Jameel Noori Nastaleeq is the standard, but test it early because its vertical metrics will surprise your layout.

    CSS logical properties: the right way to handle RTL in code

    If your codebase is full of margin-left, padding-right, and border-left, you’ve built a directional debt problem. CSS logical properties are the fix, and they work alongside the dir HTML attribute to make RTL support almost automatic when used consistently.

    The swap is straightforward in principle. Replace margin-left with margin-inline-start. Replace padding-right with padding-inline-end. Replace text-align: left with text-align: start. Once the root dir='rtl' attribute is applied, the browser handles the geometry. No separate RTL stylesheet. No duplicated rules. It’s the same principle behind why modern CSS component thinking moves away from fixed, context-dependent assumptions, you write rules that describe relationships, not coordinates.

    Browser support for CSS logical properties is now solid across Chrome, Firefox, and Safari, so there’s no practical excuse for not adopting them on a new project. The harder work is refactoring an existing codebase. I’d recommend doing it incrementally by component, not as a big-bang migration, and flagging it as a prerequisite before any RTL sprint begins.

    The mirroring rules most Western teams get wrong

    Not everything mirrors. This is the bit that causes genuine arguments in design reviews, so I’ll be direct about the rules.

    Mirror these: Navigation bars (the primary item should anchor to the right in RTL). Breadcrumbs and progress steppers. Back/forward arrows. Lists and menus. Form field layouts. Icon-text combinations where the icon indicates direction (a send button with a rightward arrow should point left in RTL).

    Do not mirror these: Clocks and clock faces (time doesn’t reverse). Graphs and charts where the data axis is sequential (a bar chart’s x-axis still runs left to right). Logos and brand assets. Video playback controls (by convention, these stay LTR globally). Telephone numbers and numeric strings.

    The hardest case is icons. An arrow that means “go forward” should flip. An arrow that means “play media” should not. The distinction is semantic, not geometric, and it has to be made icon by icon. Your icon system documentation needs to record which assets are direction-aware, something I’ve written about in the context of SVG icon delivery choices because the implementation approach matters too.

    Typographic considerations specific to Arabic and Urdu

    Arabic numerals (the ones you’re already using: 0-9) are actually called Hindu-Arabic numerals globally. But in many Arabic-language contexts, Eastern Arabic numerals (٠١٢٣٤٥٦٧٨٩) are preferred or expected. Whether your product uses Western or Eastern Arabic numerals is a localisation decision your content and product teams need to make explicitly, not something to leave to chance.

    Line height is almost always wrong on first attempt. Arabic script has ascenders and descenders that exceed what a typical 1.5 line-height setting accommodates. Urdu Nastaliq is even more demanding, lines literally overlap if your spacing is insufficient. Set line-height to at least 2 for Arabic and test Urdu Nastaliq at 2.5 or above. These are not aesthetic choices; they are legibility requirements.

    Font size also behaves differently. Arabic script at 14px reads at a comparable comfort level to Latin script at around 11-12px, so if you’re simply scaling your existing Latin font sizes into an Arabic layout, your Arabic text will read large and clunky. Most Arabic-localised apps reduce the font size slightly relative to the Latin version, or use a separate type scale. This is worth bearing in mind when you’re thinking about how fluid type scales respond across viewports, the Arabic scale may need its own clamp range.

    Testing: you cannot eyeball RTL correctness

    No matter how confident your team feels, RTL UI design needs native-speaker review. Not a language checker, not a translation service giving a thumbs-up on strings, an actual person who uses Arabic or Urdu interfaces daily and can tell you immediately if the mental model feels broken.

    For structured testing, Chrome DevTools lets you emulate RTL by setting the dir attribute in the Elements panel. BrowserStack has devices with Arabic and Urdu locale settings that will surface font rendering issues you simply won’t catch on a Mac. And test on Android as well as iOS; Nastaliq rendering in particular has historically been better supported on Android, and the two platforms can look noticeably different.

    Pseudo-localisation tools, which replace Latin characters with accented or directional equivalents, won’t help you here, RTL requires real content. Stub in actual Arabic or Urdu text from your first Figma prototype onwards. The layout surprises come from real text, not placeholders.

    Getting RTL right is genuinely complex work. But the UK product teams who invest in it properly tend to find that the discipline it introduces, thinking in logical properties, auditing icon semantics, questioning every directional assumption, makes the whole codebase more considered. That’s a side effect worth having.

  • 3D in the Browser Without Three.js: Using CSS and WebGL Alternatives on UK Client Projects

    3D in the Browser Without Three.js: Using CSS and WebGL Alternatives on UK Client Projects

    Three.js gets all the press. Type “browser 3D tutorial” into any search engine and you’ll wade through pages of Three.js content before anything else surfaces. Which is fine, it’s a genuinely capable library. But on most real UK agency projects, the performance overhead, the bundle size, and the onboarding time for a client’s in-house team make it the wrong call. I’ve watched Three.js get specced into briefs where a handful of CSS 3D transforms would have done the job in two hours and cost nothing in kilobytes. This is a comparison of the lighter approaches: CSS 3D transforms, Spline embeds, and Babylon.js. Browser 3D CSS WebGL alternatives UK agencies can actually ship without a six-week render performance audit afterwards.

    Developer working on browser 3D CSS WebGL alternatives in a UK studio environment
    Photo by MART PRODUCTION on Pexels

    Why the Three.js default is a problem for UK agency work

    The average UK agency project lives and dies by performance budgets that clients don’t fully understand until PageSpeed Insights returns a score in the forties. Three.js minified sits at around 170KB compressed. Add your scene assets, your shaders, your loaders, and you’re routinely looking at 600KB to 1MB of JavaScript before the user has seen a single polygon. On a 4G connection, still the dominant mobile standard for significant chunks of the UK outside major cities, that is a very noticeable wait.

    The Ofcom Connected Nations report consistently shows that rural UK connectivity lags behind headline 5G figures. When you’re building a product site for a client whose audience includes businesses in the Scottish Highlands, rural Wales, or large parts of East Anglia, a 1MB JavaScript payload for a spinning product model is not a sensible trade-off. That’s the first honest conversation most agencies don’t have early enough.

    CSS 3D transforms: genuinely underrated for the right jobs

    CSS 3D transforms are GPU-accelerated, require zero JavaScript dependencies, and have near-universal browser support. The perspective property, transform-style: preserve-3d, and a bit of rotateX/rotateY arithmetic will get you a cube, a card flip, a rotating logo badge, or a parallax depth effect that loads instantly and never blocks the main thread.

    I’d argue the sweet spot is anything that needs to feel three-dimensional without actually being 3D geometry. Navigation menus that flip to reveal content on hover. Hero sections with stacked layers that respond to scroll position via a few lines of vanilla JavaScript. Card components that tilt to follow the cursor. These patterns punch well above their weight visually, and the implementation is something any junior developer can maintain without specialised graphics knowledge.

    The ceiling hits you fast, though. You cannot simulate realistic lighting, you cannot load GLTF assets, and you cannot do physics. Once a client says “I want the product to rotate in 360 degrees with reflections”, CSS 3D is done.

    Spline embeds: the design-tool shortcut with real caveats

    Spline has become a genuinely popular tool in UK product design circles over the last couple of years, and the embed workflow is seductive. A designer builds a scene in the Spline desktop app, publishes it, drops an <iframe> or the Spline viewer script into the page, and it renders. No custom WebGL code. No shader authorship. For design-led agencies where the 3D work lives in the design team rather than the development team, this is a meaningful workflow improvement.

    The catch is the payload. A Spline embed typically loads the Spline runtime (around 300KB compressed) plus your scene file, which varies wildly depending on asset complexity. I’ve seen simple Spline scenes arrive at 800KB total, and complex ones comfortably exceed 2MB. For a hero animation on a landing page where performance is the conversion lever, that’s a hard sell. You’re also handing rendering control to Spline’s infrastructure, if their CDN has a bad moment, your hero goes blank.

    Where Spline earns its place is on pages where the 3D element is supplementary rather than structural: a features section, an “about us” accent, an interactive product teaser behind a scroll trigger. Used like that, with lazy loading in place and a static fallback image for slow connections, it’s a reasonable tool. The key is treating the embed like any other third-party resource: measure it, budget for it, and have a fallback.

    Babylon.js: the serious WebGL alternative most UK devs overlook

    Babylon.js is Microsoft’s open-source WebGL engine, and it’s genuinely impressive. The core engine is around 500KB minified, which is heavier than CSS transforms but lighter than a fully loaded Three.js scene with its ecosystem of add-ons. More relevantly, Babylon’s tree-shaking story is significantly better than Three.js’s: you can import only the modules you need and get meaningful bundle reduction for simpler scenes.

    Where Babylon.js earns serious consideration is the tooling ecosystem. The Babylon.js Inspector is built into the engine and available in development builds, a proper scene graph viewer, material editor, and performance profiler all in the browser. For a UK agency handing a 3D-heavy project to a client’s internal development team after delivery, that is a massive support overhead reduction. “Open the inspector, click the node, change the colour” is a conversation I can have with a non-specialist client. The Three.js equivalent involves editing raw JavaScript.

    Babylon.js also has strong TypeScript support, which matters if your agency has standardised on TypeScript across its frontend stack. The type definitions are maintained by the core team, not a third-party DefinitelyTyped contribution, so they stay current. I’ve found the GLTF loader particularly reliable, less fiddling with material conversion quirks than equivalent Three.js imports from Blender exports.

    The community is smaller than Three.js. Stack Overflow coverage thins out at the edges of the API. But the official documentation is thorough, and the Babylon.js forum is active. For greenfield projects where you’re not borrowing from existing Three.js code, it’s worth the evaluation time.

    How to choose between them on a real brief

    My decision tree is roughly this. If the brief calls for depth and parallax effects, rotating UI components, or card interactions with no actual geometry: CSS 3D, full stop. If the brief calls for a designed scene that a non-coder needs to author and the page is not performance-critical (a marketing splash, a conference landing page, something behind a gate): Spline embed with lazy loading. If the brief calls for real 3D geometry, GLTF assets, physics, or anything a client’s internal team will need to maintain and extend: Babylon.js.

    This maps roughly onto the same thinking behind choosing layout approaches, you wouldn’t use CSS Grid for a simple centred hero any more than you’d use Babylon.js for a card flip. The right tool is the lightest one that does the job. That’s a principle worth holding onto when clients arrive with references to WebGL award sites built by studios with dedicated graphics programmers and no performance requirements.

    A few other things worth noting. WebGPU is arriving properly in 2026, with Babylon.js already shipping a WebGPU backend. That’s going to change the performance ceiling for browser-based 3D significantly over the next couple of years, but it’s still an enhancement rather than a baseline. Plan for it, don’t build for it yet. On the CSS side, container queries are opening up some interesting responsive 3D composition patterns that weren’t feasible eighteen months ago; if you’re doing perspective-based layout work, they’re worth your time. And if you’re already thinking about how text sits in 3D space, the considerations around spatial typography and legibility at depth are directly relevant once you’re placing readable labels inside a three-dimensional scene.

    Performance budgeting for 3D work connects naturally to broader layout decisions. The same discipline that prevents an over-engineered WebGL scene from wrecking a Lighthouse score is the discipline behind structured grid-based layouts that don’t collapse under real-world content. And if you’re building dashboards that incorporate 3D data visualisation, the lessons from designing data-dense dashboards that actually work apply directly, complexity on screen has to justify itself in comprehension, not just in visual ambition.

    Frequently Asked Questions

    What is the lightest way to add 3D effects to a website without a JavaScript library?

    CSS 3D transforms are the lightest option: they use the GPU, require no additional JavaScript, and have full browser support. Using perspective, transform-style: preserve-3d, and rotation values, you can create card flips, parallax depth, and rotating elements with near-zero performance cost.

    Is Spline free to use on client projects?

    Spline has a free tier that allows public embeds, but commercial or private scene hosting requires a paid plan. Check the current Spline pricing page before committing it to a client project, as plan limits around scene exports and team collaboration can affect agency workflows.

    How does Babylon.js compare to Three.js for performance?

    Both engines have similar baseline capabilities, but Babylon.js has better tree-shaking support, meaning you can import only the modules you need and reduce bundle size more reliably. Babylon.js also ships a built-in inspector and stronger TypeScript types, which reduces maintenance overhead on larger projects.

  • Designing App Interfaces for the UK’s Ageing Population: What WCAG Doesn’t Tell You

    Designing App Interfaces for the UK’s Ageing Population: What WCAG Doesn’t Tell You

    WCAG compliance is the floor, not the ceiling. I’ve reviewed dozens of digital products built by UK teams who ticked every accessibility checkbox, shipped their app, and then watched users aged 60 and over abandon it within two minutes. The guidelines cover contrast ratios and keyboard navigation. They do not cover the lived reality of designing for someone who has never used a smartphone until their late fifties, has some degree of age-related macular degeneration, and finds most app navigation patterns genuinely baffling. App design for older users UK accessibility is a specialist discipline, and treating it as a WCAG checklist exercise is where most teams go wrong.

    The UK’s Office for National Statistics puts the over-60 population at roughly 16 million, and that figure keeps climbing. NHS apps, council portals, financial tools, and retail platforms all need these users. They are not a niche edge case.

    Older woman using a tablet app, illustrating app design older users UK accessibility
    Photo by Teona Swift on Pexels

    Cognitive load is the real killer, not contrast

    Working memory declines with age. That is not a controversial claim; it is well-documented cognitive science. What it means for interface design is that the number of simultaneous choices, the density of options on a screen, and the length of multi-step flows all need to come down significantly compared to what you’d ship for a general adult audience.

    I tend to think about it like this: if a 35-year-old can hold four things in their head while navigating your onboarding flow, design as if your 68-year-old user can hold two. That means progressive disclosure is not just a nice UX pattern, it is load-bearing. Break multi-step processes into single-question screens. Put one primary action per view. Cut every secondary option that does not absolutely need to exist on that particular screen.

    The NHS App is a reasonable reference point here. Its appointment booking flow keeps each step minimal, labels are plain English, and the confirmation screen repeats key information rather than assuming users retained it from two screens back. That repetition feels redundant to a younger user and essential to an older one. You cannot design for both with the same screen, and that is fine. Design for the user who needs the most support and you will not alienate the one who needs less.

    Touch target sizing: the WCAG minimum is not enough

    WCAG 2.5.5 recommends a 44×44 CSS pixel minimum for touch targets. In practice, for users over 60, particularly those with any degree of tremor, reduced fine motor control, or arthritis, 44px is still small. Research from the Nielsen Norman Group puts comfortable touch target size for older users closer to 60x60px, with generous spacing between adjacent targets to reduce mis-taps.

    Mis-taps are a huge source of frustration. When a user taps the wrong button repeatedly, they lose confidence in the interface and in themselves. That confidence collapse is much harder to recover from in an older user than a younger one. I’d argue the emotional cost of a mis-tap is asymmetric across age groups, and that asymmetry should drive your sizing decisions.

    Some practical rules I use: make primary call-to-action buttons at least 56px tall, keep destructive actions (delete, log out, cancel) physically separated from confirmatory ones, and never place two tappable elements closer than 12px apart. On a related note, the accessible palette decisions UK product teams often get wrong apply doubly here, older users have reduced contrast sensitivity that compounds the touch accuracy problem when buttons are poorly differentiated visually.

    Font legibility goes further than size

    The standard recommendation is 16px body text minimum. For interfaces targeting users over 60, I’d go to 18px as a floor, with headings at 24px or above. But size is only part of font legibility. The typeface choice itself matters enormously.

    Age-related changes to the lens of the eye reduce sensitivity to fine detail, which is why highly stylised typefaces, thin weights, and fonts with low x-heights become genuinely difficult to parse. Avoid any font weight below 400 for body copy. Prefer humanist sans-serifs, Inter, Atkinson Hyperlegible, or system UI fonts, over geometric ones. Atkinson Hyperlegible was specifically designed for users with low vision and performs exceptionally well in this context; the British Dyslexia Association also endorses it for overlapping reasons.

    Line height matters too. Tight leading, common in sleek modern UI, forces the eye to work harder to track from one line to the next. Set line height at 1.6 or above for body text. And if you are using fluid typography with CSS clamp(), make sure your minimum clamp value never drops below 18px in older-user-facing contexts, even on small viewports.

    NHS-informed interaction patterns worth stealing

    The NHS design system is publicly available and genuinely excellent. The team that built it consulted heavily with patients across age groups, and the patterns reflect real-world usability testing rather than theoretical best practice. A few things in particular stand out.

    First: plain English labels over clever microcopy. “Continue” beats “Let’s go.” “Your appointments” beats “My health hub.” Older users map UI labels directly to mental models formed outside digital contexts. The closer the label matches the real-world concept, the faster comprehension happens.

    Second: explicit error messages that tell users exactly what to fix. “Invalid input” is useless. “Your date of birth should be entered as DD/MM/YYYY, for example, 15/03/1958” is useful. The NHS form patterns specify this level of specificity as standard, and it drastically reduces abandonment in older cohorts.

    Third: visible, persistent navigation. The hamburger menu is a learned pattern that many older users simply never learnt. If your app can run with a bottom tab bar or persistent side navigation rather than a hidden drawer, do it. The cognitive cost of remembering that the menu is behind a three-line icon is higher than it looks.

    You can read more about how strong institutional UI work informs everyday product design in my piece on the design principles behind BBC iPlayer, a lot of the same “reduce ambiguity, increase predictability” logic applies across user groups.

    Testing with real users, not assumptions

    The single most common mistake I see is teams designing for the mythical “older user” based on assumptions rather than running sessions with actual people aged 60 and over. It is not the same thing. The variance within that group is enormous, a 62-year-old software developer has completely different needs from a 78-year-old who has owned a smartphone for six months.

    Age UK runs digital skills programmes across the UK and has published research on common barriers older users face with digital services. Their findings consistently point to confidence and trust as primary blockers alongside the interaction design issues covered above. Users need to feel that the app will not do something unexpected, will not lose their data, and will always give them a clear way back. Irreversible actions without confirmation dialogs are particularly damaging to trust in this cohort.

    Recruit test participants through local libraries, NHS patient groups, or organisations like Age UK. Run tasks, observe without prompting, and note every hesitation. Those hesitations are your redesign brief.

    The data visualisation and information density decisions that come up in ageing-population app design also map directly onto the broader challenge of designing data-dense interfaces that actually work, the same principle of ruthless information hierarchy applies whether your user is 30 or 70.

    WCAG is necessary. It is not sufficient. Get the cognitive load right, size your targets properly, pick legible type, borrow from the NHS design system, and test with real older users. That is the actual brief.

  • Astro vs Next.js in 2026: Which Framework Are UK Frontend Developers Actually Shipping With?

    Astro vs Next.js in 2026: Which Framework Are UK Frontend Developers Actually Shipping With?

    The Astro vs Next.js 2026 debate hasn’t quietened down. If anything, it’s got louder, and for good reason. Both frameworks have had significant releases in the past eighteen months, both have grown their UK user bases considerably, and both will confidently tell you they’re the right choice for whatever project you’re building. They’re not both right. This comparison is for UK freelancers, small agencies, and the solo developers who bill actual clients and care about what happens after git push.

    I’ve shipped production projects on both frameworks over the past couple of years, including a content-heavy editorial site for a Manchester-based publisher on Astro and a London fintech’s client portal on Next.js. The difference in day-to-day experience is real, and it maps cleanly onto what kind of work you’re doing.

    Developer reviewing Astro vs Next.js 2026 code on a laptop at a modern desk
    Photo by Daniil Komov on Pexels

    What each framework actually does

    Next.js is a React meta-framework. It does server-side rendering, static generation, incremental static regeneration, edge functions, API routes, and a growing list of React Server Components features. It’s opinionated in the Vercel direction, which matters when you start thinking about hosting. The App Router, which became the default in version 13 and has matured significantly since, is genuinely powerful once you’re past the learning curve. It’s also genuinely confusing until you are.

    Astro is a different beast. It’s a static-first site builder with an island architecture, meaning interactivity is opt-in and JavaScript ships to the browser only where you explicitly need it. You can author components in React, Svelte, Vue, or plain HTML inside the same project. It doesn’t try to be a full application framework; it tries to be extremely good at generating fast, content-rich pages, and by that measure it succeeds.

    Build performance: where Astro genuinely wins

    Astro’s build times are fast. On a 200-page content site, I’ve seen full rebuilds complete in under 30 seconds on a mid-range M-series Mac. Next.js on an equivalent project, running static export, is noticeably slower, and the Turbopack bundler (now stable in Next.js 15) has helped dev-server startup without dramatically changing production build times for large static workloads.

    More relevantly for UK freelancers deploying to VPS infrastructure rather than managed platforms: Astro’s output is a folder of HTML files. There’s nothing to run. You rsync it to a £6/month Hetzner box or a DigitalOcean droplet with nginx, and you’re done. No Node.js process to keep alive, no PM2 config, no memory creep at 3am. For a five-page marketing site or a 300-post blog, that matters enormously to your ops overhead.

    Next.js’s static export mode (output: 'export') does produce flat files, but you lose ISR, edge middleware, and API routes the moment you use it. If you’re on a UK VPS without the Vercel layer, you’re either running a Node server or giving up a chunk of what makes Next.js compelling. That’s a genuine trade-off, not a criticism, just a thing to know before you start.

    Terminal build output during an Astro vs Next.js 2026 performance comparison
    Photo by Godfrey Atima on Pexels

    Hosting costs on UK infrastructure

    Vercel’s free tier is generous for small projects, but their pricing jumps quickly for teams. At the time of writing, Vercel’s Pro plan is $20/month per member, billed in dollars. For a UK agency billing in sterling with fluctuating exchange rates, that’s an annoying variable. I know several small agencies in the UK who’ve moved Next.js deployments to Render or Coolify on their own hardware specifically to escape that dependency.

    Astro sites on flat hosting are almost embarrassingly cheap. Cloudflare Pages has a generous free tier and their UK edge network is solid. Netlify works fine too. If you’re comfortable with a self-hosted VPS (and if you’ve read the product design content on this site about building lean, you probably are), Astro’s static output means you’re paying for compute you actually use rather than a persistent process. For a busy content site, that’s a real saving over twelve months.

    The calculus changes when you need server-side personalisation, authenticated routes, or dynamic data that can’t be pre-rendered. That’s Next.js territory, and at that point the hosting cost is the cost of the capability, not framework bloat.

    Which project type suits which framework

    I’d pick Astro for: marketing sites, content blogs, documentation sites, portfolio sites, any project where the content is known at build time, any client who doesn’t want to pay for a Node server indefinitely. Astro’s MDX support is excellent, its image optimisation pipeline is genuinely good, and the lack of JavaScript overhead means Core Web Vitals scores that make clients happy. If you’re working on something like a data-heavy editorial layout, the structured grid patterns popular in British editorial design render beautifully in Astro with zero client JS cost.

    I’d pick Next.js for: SaaS products, client dashboards, anything with real-time data, e-commerce with personalisation, applications that need API routes and server-side auth. If you’re building a fintech product that has to comply with FCA requirements and needs server-side session handling, Astro is not the right tool and nobody should pretend otherwise. The React Server Components model in Next.js 15 is genuinely well-suited to complex, data-driven interfaces. The kind of dense, multi-panel dashboards we’ve covered in depth before, for instance data dashboard design for UK government-style tools, need the application-layer features Next.js provides.

    The developer experience gap is closing, but not closed

    Astro 5 introduced server islands and a content layer API that makes it much more capable for hybrid content and dynamic situations. It’s not a full application framework yet, but it’s no longer purely a static tool either. The TypeScript experience in both frameworks is good; Astro’s type inference for content collections is particularly clean, which I find genuinely satisfying in a slightly nerdy way.

    Next.js’s App Router DX still has rough edges. The mental model for caching in Next.js 15 has been reworked (again), and while it’s better, it’s still the part of the framework most likely to produce a support ticket from a confused junior developer on your team. If you’re a solo freelancer who needs to hand a project off to a client’s in-house team of non-specialists, Astro’s conceptual simplicity is an underrated advantage.

    One practical note: if you’re doing any SEO work on behalf of clients, the framework choice affects how you structure pages and metadata. It’s worth checking your on-page implementation against a tool like searchenginetuning.co.uk once you’ve done a build, regardless of which framework you used, since routing differences between the two can create subtle metadata issues that only show up on inspection.

    The honest verdict for 2026

    The framing of “which is better” misses the point. Astro is better for content sites; Next.js is better for applications. The more useful question for UK freelancers is: what’s the default you reach for when a project brief is vague?

    My default is now Astro. Most of the briefs I get are for marketing sites, documentation, or content-led projects where the client doesn’t actually need server-side rendering. Starting with Astro keeps costs low, keeps the architecture simple, and keeps my clients’ Lighthouse scores high. When a project genuinely needs what Next.js offers, I switch without much drama, because the migration path is clear and the use case is obvious by that point.

    The GOV.UK Service Manual guidance on choosing technology has a principle I keep coming back to: choose the simplest thing that meets your needs. For most UK freelance web work in 2026, that’s Astro. For product teams building SaaS, it’s Next.js. Both frameworks are mature, both have futures, and neither is going to embarrass you in a client meeting.

  • Designing Cookie Consent Flows That Don’t Destroy Your Conversion Rate: A UK PECR and ICO Compliance Guide

    Designing Cookie Consent Flows That Don’t Destroy Your Conversion Rate: A UK PECR and ICO Compliance Guide

    Here is the uncomfortable truth about most cookie banners: they were designed by lawyers, not designers. You can tell. They sit there like a passive-aggressive sticky note, blocking your content, hiding the reject button in a font size that would strain the eyes of someone half my age, and generally making a first impression that screams “we don’t actually want you to say no.” That is not just aesthetically grim, it is increasingly illegal. The ICO’s 2024 enforcement push against non-compliant consent mechanisms made clear that cookie consent UI design UK ICO PECR compliance is not a technicality you can paper over with a barely-visible opt-out link.

    This piece goes through what PECR actually demands, what the ICO’s published guidance says about interface design specifically, and how to build a consent flow that is both genuinely compliant and not a conversion catastrophe. Because those two things are not mutually exclusive, they just require some actual design thinking.

    Clean cookie consent UI design showing equal-weight accept and reject buttons for UK ICO PECR compliance
    Photo by Pixabay on Pexels

    What PECR actually requires (without the legalese)

    The Privacy and Electronic Communications Regulations 2003, PECR, sit alongside the UK GDPR and are enforced by the ICO. For cookies specifically, PECR requires prior, informed, freely given consent before any non-strictly-necessary cookie is set. That is the short version. The full version has some sharp teeth.

    “Freely given” is where most implementations fail. If you pre-tick boxes, if accepting is one click but rejecting requires three, if you grey out the reject button or bury it in a “Manage preferences” submenu that takes six taps to navigate, that is not freely given consent. The ICO has been explicit: consent obtained through designs that nudge users towards acceptance does not meet the standard. They call these dark patterns, and they have published specific examples of what counts as one.

    Strictly necessary cookies, those needed to make the site function, like session cookies, do not need consent at all. Everything else does. Analytics, advertising pixels, A/B testing scripts, heat-mapping tools, embedded YouTube players: all of these require prior opt-in unless you are routing them through a proxy that strips identifiers. That last option is genuinely worth exploring if analytics are your only concern, but it is out of scope here.

    The dark patterns the ICO specifically flags

    I would recommend reading the ICO’s updated cookie guidance directly, because it is more specific than most designers expect. The patterns they name include:

    Asymmetric prominence. The “accept all” button is large, colourful, and high-contrast. The “reject” or “manage” option is smaller, greyed out, or styled as a text link. This visual hierarchy communicates a preference, which means consent is not freely given.

    Forced interaction. Making users click through multiple screens to reject, whilst acceptance is one button. If it takes three steps to say no and one step to say yes, you have built a funnel, not a consent flow.

    Confusing language. “Allow partners to use personalised data” is not informed consent. People need to understand what they are agreeing to. Plain English is not optional.

    Consent walls. Blocking content access unless the user accepts all cookies. The ICO’s position is that this can undermine the “freely given” requirement, particularly where genuine alternatives to the service do not exist.

    UI patterns that work without being ugly

    The good news is that compliant does not mean clunky. Here is how I would build this.

    Symmetric button styling

    Give “Accept all” and “Reject all” equal visual weight. Same button size, same border treatment, same font weight. The only permissible difference is colour, and even then, use brand colours rather than a bright green accept versus a faded grey reject. Monochrome button pairs work particularly well: both buttons outlined, no fill, identical typography. Clean, considered, legally sound.

    Three-option layouts

    The most practical pattern for most UK product teams is a three-button row: “Reject all”, “Manage preferences”, “Accept all”. This gives users the quick binary choice if they want it, whilst making granular control available without friction. Put “Reject all” on the left, counterintuitive, but it signals confidence rather than reluctance. Users who are privacy-conscious will find it immediately; users who genuinely want to accept everything will still accept.

    The modal vs banner question

    Banners (bottom or top strips) tend to generate higher interaction rates but lower comprehension. Modals with a backdrop generate better-informed choices but disrupt flow more severely. For most sites, a centre-screen modal on first visit is the right call, it signals that the choice matters without being dismissive. Avoid full-screen takeovers; they are aggressive and associate your brand with the very dark patterns you are trying to avoid.

    Preference panels worth building

    The preference panel (reached via “Manage preferences”) is where most implementations fall apart. I have seen panels that list seventeen toggle switches with descriptions like “Audience measurement partner 4”, completely useless to an ordinary person. Build category-level toggles: Analytics, Marketing, Personalisation. Describe each in one sentence of plain English. Show which third parties are involved, but summarise rather than enumerate. Make the panel closeable without forcing a decision, some users just want to read before choosing.

    If you are building this in a component library, the preference panel is a good candidate for a compound component pattern: a top-level ConsentPanel that takes category configs as props, renders toggle groups with associated descriptions, and emits a structured consent object that your tag manager or consent management platform can consume. Keep the state management simple, a flat object keyed by category slug is all you need.

    Colour, contrast and accessibility

    This connects to accessibility work more broadly. If you have been following guidance on designing for older users and accessibility for the over-55 audience, most of it applies directly here. Consent flows need to meet WCAG 2.2 AA contrast ratios; the ICO’s own published guidance references accessibility standards. Minimum 4.5:1 for body text, 3:1 for large text. Focus states on all interactive elements. No relying on colour alone to distinguish accept from reject.

    Typography matters here too. Do not use a font size below 14px in a consent banner. Sixteen is more defensible. If you have been thinking carefully about your typography stack and system fonts, a consent modal is a good place to use a system font stack, it loads instantly, which matters given that consent must be presented before non-essential scripts fire.

    Technical implementation notes

    A few things I see trip up developers who are building consent flows from scratch rather than using a consent management platform (CMP).

    First, no cookies should be set before consent is given. This sounds obvious but is violated constantly. If your analytics script fires on page load regardless of consent state, you are non-compliant before the banner has even rendered. Consent must gate script execution, not just set a preference that is read later.

    Second, consent must be recorded and reproducible. You need to store what the user consented to, when, and which version of your policy was active at the time. A UUID per consent event, stored server-side, is the clean solution. Storing consent state only in a cookie is weakly circular and difficult to audit.

    Third, consent must be withdrawable as easily as it was given. If your site has a footer link labelled “Cookie settings” that opens your preference panel, which was the mechanism for giving consent, that satisfies this requirement neatly. Make sure it actually works, and make sure it re-presents the panel with the current state rather than defaulting to all-off.

    If you are building empty states into your consent flow (think: the preference panel before any categories have been toggled), there is a nice pattern for communicating status clearly covered in the piece on designing effective empty states for UK SaaS onboarding, the principle of communicating what will happen next applies well here.

    Does any of this actually hurt conversion?

    The honest answer is: a well-designed compliant consent flow will reduce your analytics data compared to a dark-pattern banner that coerces acceptance. That data was always unreliable anyway, because coerced consent produces noise rather than signal, people who clicked accept reflexively are not the engaged users your analytics should be modelling.

    What it will not do is hurt your conversion rate in the sales funnel sense, provided the consent flow does not block content access. A modal that appears, presents a genuine choice, and dismisses cleanly adds perhaps two seconds to a user’s first visit. That is negligible against the reputational and legal cost of an ICO enforcement notice, which can run to fines of up to £500,000 under PECR, or higher under the UK GDPR framework that sits alongside it.

    Build the consent flow as a real UI component, not an afterthought. Treat it with the same design rigour you would give a checkout flow or an onboarding sequence. That is the entire argument.

    Frequently Asked Questions

    What does PECR require for cookie consent in the UK?

    PECR requires that you obtain prior, informed, and freely given consent before setting any non-strictly-necessary cookies. This means no pre-ticked boxes, no dark patterns that push users towards acceptance, and the ability to withdraw consent as easily as it was given. Strictly necessary cookies, session management, shopping baskets, are exempt.

    Is a cookie banner enough to be ICO-compliant, or do I need a full consent management platform?

    A banner alone is not sufficient; what matters is that non-essential scripts are genuinely blocked until consent is given, that preferences are recorded with a timestamp, and that users can change their choice later. A consent management platform (CMP) handles most of this, but you can build a compliant system from scratch if you implement the technical requirements correctly. The ICO does not mandate any specific tooling.

    Are dark patterns in cookie consent actually enforced in the UK?

    Yes. The ICO issued enforcement notices against major UK publishers in 2024 specifically citing non-compliant consent interfaces, including asymmetric button styling and buried reject options. Fines under PECR can reach £500,000, and the ICO has stated publicly that enforcement in this area is ongoing.

    Can I style my accept button differently from the reject button?

    Using different colours is acceptable provided there is no significant difference in prominence, size, font weight, and placement should be equal. The ICO’s guidance specifically flags making the reject option visually subordinate as a dark pattern. Monochrome, equal-weight button pairs are the safest approach.

    Does a cookie consent modal affect page load performance or Core Web Vitals?

    It can, but the impact is manageable. Using a system font stack in your consent modal avoids any additional font-loading overhead. The modal itself should be lightweight HTML and CSS, ideally inlined, so it renders before external scripts fire. Blocking non-essential third-party scripts until consent is given can actually improve your initial load performance, as those scripts are often the heaviest assets on the page.

  • How to Design Effective Empty States: The UI Pattern UK SaaS Products Consistently Underestimate

    How to Design Effective Empty States: The UI Pattern UK SaaS Products Consistently Underestimate

    Empty states are the screens nobody designs until the last sprint. You know the ones: the blank table with no rows, the notification panel with nothing in it, the inbox on day one. Most UK SaaS products treat these moments as edge cases. I’d argue they’re some of the most consequential screens in your entire product, and getting them wrong is quietly killing your activation rates.

    The idea behind empty state UI design is simple enough. When a user has no data, no content, and no history in a given context, what do they see? If the answer is a white box and a sad little grey sentence reading “No items found”, you’ve essentially handed a new customer a locked room and no key.

    Smartphone displaying an empty state UI design example relevant to SaaS UK product teams
    Photo by Tima Miroshnichenko on Pexels

    Why empty states matter more than your onboarding flow

    There’s a tendency in UK SaaS product teams to spend weeks on the formal onboarding wizard, complete with progress bars and illustrated walkthroughs, and then dump the user into an empty dashboard with zero guidance. The problem is that onboarding ends. Empty states don’t. A user who creates a new project, a new workspace, a new report, or a new team will hit an empty state every single time they do something new. That’s not an edge case; it’s a recurring event across the entire user lifecycle.

    Slack does this brilliantly. Every new channel you create opens with a short message explaining the channel’s purpose and what a first message might look like. It costs almost nothing to implement and transforms what would be a void into a prompt. Notion does something similar with placeholder text in empty databases. These aren’t accidents; they’re the result of product teams treating empty states as part of the core UX, not an afterthought.

    The three types of empty state and how to handle each one

    Not all empty states are the same, and designing a single generic treatment for all of them is a mistake. I tend to split them into three categories.

    First-use empty states appear when someone uses a feature for the first time and has no data yet. This is prime onboarding real estate. The design should explain what the feature does, show the user what the filled state will look like (even if just as an illustration), and give them a single, clear call to action. One button. Not three. The copy should be active and encouraging without being patronising.

    No-results empty states happen after a search or filter returns nothing. These are different because the user has intent. They were looking for something specific. The design here should acknowledge what they searched for, suggest alternatives (fix the filter, broaden the search, check the spelling), and avoid making the user feel like they’ve hit a wall. If your product is a UK-facing B2B tool and someone searches for an invoice that doesn’t exist, the worst possible response is just “No results”.

    Cleared or completion states occur when a user has genuinely finished something: an empty inbox, a completed task list, a fully processed queue. These deserve a moment of positive reinforcement. Something brief that says “you’re done” works better than the same blank void that appears everywhere else. The tone should feel different to the first-use state, because the context is totally different.

    Copy is doing 70% of the work

    Most empty state design discussions focus heavily on the illustration. And look, a well-crafted illustration helps. But I’ve reviewed products where a decent illustration sat above catastrophically bad copy, and the screens were still useless. The copy is doing most of the heavy lifting.

    A few principles I come back to repeatedly. First: lead with what the feature does, not with what’s missing. “You haven’t added any team members yet” is worse than “Invite your team to start collaborating on projects.” One focuses on absence; the other focuses on possibility. Second: keep the call to action on the screen and make it obvious. Users who hit a blank state shouldn’t have to navigate somewhere else to fix it. If the empty state is a contacts list, there should be an “Add contact” button right there on the screen. Third: match the tone of your product voice. A fintech product aimed at UK accountants shouldn’t suddenly go whimsical with a cartoon dog in the empty state. Consistency matters.

    The GOV.UK Service Manual has good underlying principles about clarity and plain language that translate usefully into SaaS copy, even if the style is obviously different. The core idea, that users shouldn’t need to think hard about what to do next, applies everywhere.

    Illustrations: useful tool or distraction?

    The illustrated empty state became almost universal in SaaS products around 2019 and I’ve watched it calcify into a cliché. Teams reach for an undraw.co illustration, drop it above a heading and a button, and call it done. That’s not necessarily wrong, but it’s rarely right either.

    An illustration earns its place when it genuinely communicates something: the nature of the feature, what the filled state might contain, or a sense of scale. It doesn’t earn its place when it’s decorative filler that adds loading weight without adding meaning. For mobile especially, an oversized illustration that pushes the call to action below the fold is doing active harm.

    If you’re working on a product that needs to feel serious, like a compliance tool, a legal SaaS, or anything facing UK regulated industries, an illustrated empty state can actually undermine trust. A clean typographic treatment with precise copy often performs better than anything illustrated. Test it. Don’t assume.

    Empty states in dashboards: the hardest case

    Dashboard empty states are particularly awkward because dashboards are often module-based. You might have eight widgets and only three have data yet. The result is a patchy grid of partially populated information and multiple empty modules, each with their own small sad state. This fragments the experience badly.

    There’s a strong argument for using a progressive disclosure approach here: show only the modules the user has data for, surface the others as locked or greyed-out with a prompt to set them up. This is the approach I’ve seen work well in UK SaaS products that handle analytics or project management. It’s related to a broader point about structured layout thinking; the article on why British editorial and SaaS sites are returning to structured grid layouts has some useful thinking about how composition affects perceived completeness.

    For data-heavy dashboards specifically, there’s real value in pre-populating with sample data. Show the user what their dashboard could look like with placeholder numbers, labelled clearly as sample data, so they understand the value proposition before they’ve put any real information in. Notion does a version of this with their templates. It’s honest, it’s useful, and it turns what would be a confusing blank grid into an immediate demonstration of product value. More on designing those kinds of interfaces in the piece on designing data-dense dashboards that actually work.

    Testing your empty states properly

    Most teams test empty states by manually clearing their test account and looking at the screen. That’s better than nothing. But the real test is watching a real new user hit an empty state for the first time and seeing what they do. Do they understand what the feature is for? Do they know what to do next? Do they feel lost or do they feel guided?

    User testing sessions with participants who are genuinely unfamiliar with the product are invaluable here. If your team is based in the UK and doing any kind of remote testing through tools like Maze or UserTesting, recruit participants who match your actual user profile, not just whoever is convenient. You’ll learn things from a 45-year-old accountant in Birmingham navigating your empty transaction list that you’d never catch internally.

    Typography also matters on these screens more than people expect. A well-chosen typeface with the right weight and spacing can make empty state copy feel considered rather than thrown together. If you’re still using system defaults without thinking about hierarchy, the piece on building a proper typography stack in 2026 is worth your time.

    The bottom line is that empty states are not maintenance work. They’re product design work. Every time a UK SaaS product ships a blank white screen with two words of placeholder text, it’s leaving activation, retention, and trust on the table. The fix isn’t expensive. It’s just attention, and a bit of craft applied to the moments most teams skip.

  • Icon Fonts Are Dead, But Are SVG Sprites Still Worth Using in 2026? A UK Frontend Developer’s Take

    Icon Fonts Are Dead, But Are SVG Sprites Still Worth Using in 2026? A UK Frontend Developer’s Take

    Icon fonts had a good run. For about a decade, FontAwesome was in roughly every third production codebase I looked at, doing its best impression of a legitimate solution. Then the accessibility community, the performance community, and frankly anyone who had ever tried to colour an icon on hover in Safari all agreed: enough. Icon fonts are genuinely gone now, and good riddance. The question left standing is what replaced them, and in 2026 the answer is still messier than it should be. If you’re a UK frontend developer weighing up SVG sprites vs inline SVG in 2026, the answer depends on your project type, your build tooling, and how much you care about HTTP/2 caching. Let me break it down properly.

    Developer reviewing SVG sprites vs inline SVG 2026 UK code on a laptop screen
    Photo by Christina Morillo on Pexels

    The three approaches, quickly

    Before benchmarks, a quick reset on what we’re actually comparing. Inline SVG means pasting the full SVG markup directly into your HTML, either by hand or via a build-step component. Every icon is its own lump of XML in the DOM. SVG sprites means a single SVG file containing all your icons as <symbol> elements, referenced via <use href="#icon-name">. Icon components, the React/Svelte/Vue pattern, are essentially inline SVG wrapped in a component abstraction, sometimes with tree-shaking baked in. Each has legitimate uses. None is universally correct.

    What the performance numbers actually say in 2026

    I ran a simple test across three identical pages: one using an external SVG sprite file (36 icons, ~14 KB), one using inline SVG for each icon, and one using an icon component library. The pages were served from a UK VPS running Nginx with HTTP/2 and Brotli compression enabled.

    The sprite approach loaded the icon asset in a single cached request. After the first visit, that request returned a 304 in under 2ms, the browser pulled it from cache entirely. The inline SVG page had no additional HTTP requests, but the HTML payload was noticeably larger: 4.1 KB heavier for a page with 12 icon usages. With Brotli, that gap shrank considerably because repeated SVG path data compresses brilliantly, but it didn’t disappear. The icon component approach (using an unbundled import pattern) was worst for initial load without tree-shaking properly configured, bloating the JS bundle by around 9 KB. With tree-shaking, it matched inline SVG closely.

    The GOV.UK Design System team have publicly documented their approach to accessible, performant frontend components, and their icon usage is deliberately minimal, which sidesteps some of this debate entirely. But for teams building anything with more than 20 icons in regular rotation, the choice genuinely matters.

    When SVG sprites still make sense

    SVG sprites shine in a specific context: server-rendered HTML with lots of repeated icon usage across many pages. A GOV.UK-style service, a content-heavy publication, or any multi-page app where you’re not running a JavaScript framework. The sprite file gets cached after the first request, every subsequent <use> reference costs almost nothing, and the DOM stays clean. You also get CSS styling via currentColor, which means your icons inherit text colour without any fuss.

    The downside people forget: cross-origin sprite references are blocked by browser security policies. Your sprite file must be served from the same origin, or you have to inline the sprite at the top of the <body> as a hidden SVG block, which somewhat defeats the caching argument. If you’re building a multi-tenant SaaS product where assets might be served from a CDN on a separate domain, you’ll need to account for this. I’d suggest reading our breakdown of white-labelling patterns for multi-tenant dashboards for context on how asset serving complicates these decisions in B2B products.

    When inline SVG wins

    Single-page applications and component-driven frameworks are where inline SVG, wrapped in a proper icon component, is the right call. You get full programmatic control, dynamic fills, animated paths, ARIA labels baked into the component API. Tree-shaking means you only ship the icons you actually use. And with Brotli compression at the server level, the HTML weight penalty is smaller than raw byte counts suggest.

    I’d also pick inline SVG for anything accessibility-critical. Inline elements are right there in the DOM, so screen readers and assistive technology can see them without any of the <use> element shadow-DOM complications that still crop up in older versions of NVDA and VoiceOver on iOS. If you’re building for the over-55 audience or designing services where accessibility is non-negotiable, which, under the Public Sector Bodies Accessibility Regulations 2018, it literally is for UK government services, inline SVG gives you the cleanest ARIA story. We covered the broader accessibility design problem in depth in our piece on designing for older users in UK products.

    The 2026 tooling landscape changes things

    Here’s where it gets interesting. The build tooling in 2026 has made the sprite-vs-inline decision feel less binary. Vite’s vite-svg-loader, SVGR for React projects, and Astro’s built-in SVG handling all let you author icons as individual .svg files and choose at build time how they get emitted. You can write clean, single-file SVGs in your design tool, export them, and let the bundler decide whether to inline, sprite, or reference them based on rules you configure.

    This is genuinely useful. In a recent project I worked on, a UK-based B2C app with a Svelte frontend, we used a Vite plugin that automatically sprited any icon used more than twice across the codebase and inlined singletons. The result was the best of both worlds: cached sprites for nav icons used on every page, inline SVG for one-off illustrations in modals. Total icon payload was 8.3 KB, cached after first visit.

    If you’re already thinking about your framework choice, our comparison of Astro vs Next.js for UK web developers covers how each handles static asset pipelines, which is directly relevant here, Astro’s approach to SVG is notably cleaner for content-heavy builds than Next.js’s default configuration.

    Which approach fits which UK project type

    GOV.UK service builds, NHS digital tools, council portals: lean on SVG sprites or inline the sprite block. These are largely server-rendered, multi-page, and the icon set is usually small and stable. Performance and accessibility over cleverness.

    Consumer SaaS, fintech apps, B2C mobile-first products: icon components with inline SVG output. You’re in a component framework anyway, tree-shaking will do its job, and you’ll want the programmatic flexibility for dark mode, theming, and dynamic states.

    Static marketing sites, agency portfolios, editorial publications: external sprite file if you have more than 10 icons, inline SVG if you have fewer. Don’t overthink it. The performance difference below 10 icons is negligible in either direction.

    The icon font question, revisited briefly

    Someone always asks. No, icon fonts are not making a comeback. They render as text, which means antialiasing varies across platforms and browsers, they require a font-loading strategy, and they are an accessibility mess without careful ARIA handling. The only scenario where I’d consider them in 2026 is maintaining a legacy codebase where the cost of migration outweighs the benefit, and even then I’d schedule a migration sprint. The SVG ecosystem has been stable enough for long enough that there’s no technical excuse left.

    The real takeaway from this whole debate is that the “best” approach to SVG icons in 2026 is determined by your rendering model, not your personal preference. Know how your HTML is being generated. Know where your assets are being served from. Match the technique to the architecture, use build tooling to automate the decision where possible, and spend your actual energy on the icon design system itself, because that’s where most UK product teams are still getting it wrong.

    Frequently Asked Questions

    Are SVG sprites better than inline SVG for performance in 2026?

    It depends on your rendering model. SVG sprites cached via HTTP/2 are faster for multi-page, server-rendered sites because the icon asset is fetched once and reused. Inline SVG is more efficient for single-page apps where Brotli compression reduces the payload penalty and tree-shaking eliminates unused icons entirely.

    Can I use SVG sprites from a CDN on a different domain?

    No, browsers block cross-origin references due to security policies. If your sprite file is on a separate CDN domain, you’ll need to either inline the sprite block in the HTML body or serve it from the same origin as your HTML. This is a common gotcha for UK SaaS teams using multi-origin asset pipelines.

    Which SVG icon approach is best for GOV.UK or NHS digital services?

    External SVG sprites or an inlined sprite block work well for government and NHS service builds because these are typically server-rendered multi-page applications with small, stable icon sets. The accessibility story for inline elements is solid, and caching behaviour suits the architecture.

    Do icon components in React or Svelte just produce inline SVG?

    Yes, in most cases. Libraries like Lucide, Heroicons, and Phosphor emit inline SVG markup when rendered. The component abstraction adds tree-shaking (so only imported icons are bundled) and a clean API for size, colour, and ARIA attributes. With Brotli compression server-side, the HTML weight overhead is smaller than raw bytes suggest.