Author: Sophie Davis

  • How British Newspapers Are Redesigning Their Digital Subscriptions UX to Stop the Bleed

    How British Newspapers Are Redesigning Their Digital Subscriptions UX to Stop the Bleed

    British newspapers are in a genuinely fascinating position right now. They are simultaneously some of the oldest media brands on the planet and some of the most aggressive experimenters in digital product design. The Guardian, The Times, and The Telegraph have all had to figure out how you take a reader who once paid 50p at a newsagent and turn them into someone who feels good about a recurring monthly charge on their bank statement. That is not a trivial UX problem. I’ve spent a good chunk of time lately pulling apart their subscription flows, poking at their dashboards, and thinking about what the patterns actually reveal for product designers beyond the media world.

    Person reading a UK digital news subscription on a tablet, illustrating UK digital subscription UX design
    Photo by Letícia Alvares on Pexels

    The paywall is not the product, the moment before it is

    Every UK publisher has iterated endlessly on where to cut off free reading, but the more interesting design decisions happen in the friction zone just before the paywall triggers. The Times uses a metered model with a visible article counter, which does something psychologically clever: it turns scarcity into a UI element. You watch your remaining free articles tick down, and that countdown is doing real persuasion work without a single line of copy. The Guardian goes the opposite direction with its contribution model, which is a fascinating choice to study. There is no hard cut-off. Instead, a persistent but low-aggression banner asks you to fund journalism you are already consuming. The design hierarchy keeps content dominant and the ask secondary, which builds trust over time rather than manufacturing urgency.

    The Telegraph’s approach sits closer to The Times: a hard paywall with a clearly staged free trial CTA. What I find worth studying there is how the pre-paywall article experience is designed. Images load fully, the lede paragraph renders completely, and the truncation point is selected to land just after the reader has committed enough attention to feel the loss. That is not an accident. Someone made a deliberate call about where narrative investment peaks and placed the gate there. That is solid conversion thinking applied to editorial product design.

    What subscriber dashboards reveal about retention thinking

    Once someone subscribes, the dashboard becomes the product. And honestly, this is where UK publishers still have the most room to grow. I logged into The Times subscriber account area recently and the experience felt like it was designed by a different team than the one running the front end. The reading experience is genuinely polished; the account management section is utilitarian at best. Billing details, newsletter preferences, and device management all exist, but the information architecture feels like a compliance checklist rather than a designed space.

    The Guardian does marginally better here by surfacing reading history and letting subscribers access specific series or long-form pieces they have saved. That saved-content mechanic is doing important retention work: it gives people a reason to return to the account area, which reinforces the sense of an ongoing relationship rather than a passive payment. Any product designer building a subscription interface should be thinking hard about how the dashboard communicates ongoing value to a user who has already committed. The paywall converts; the dashboard retains.

    The data visualisation opportunity here is largely untapped. Imagine a subscriber dashboard that showed you your reading habits over the past month, surfaced topics you engage with most, and recommended content based on actual behaviour. Spotify does this obsessively. Netflix does it. UK publishers have the content depth to do it and mostly do not. There is a design pattern waiting to be built there, and whoever ships it first will have a meaningful retention advantage.

    Cancellation flows and the ethics of friction

    This is where UK digital subscription UX design gets politically charged. The dark pattern conversation has moved firmly into regulatory territory: the FCA and the Competition and Markets Authority have both signalled attention to subscription practices, and the Consumer Rights Act 2015 sets a clear baseline for what fair cancellation looks like. Hiding the cancel button five menus deep is not just bad UX; it is increasingly a legal risk.

    The Times handles cancellation reasonably well compared to some, routing you through a retention offer screen before confirming. The offer is visible, the decline path is clear, and the confirmation is immediate. That is a pattern worth copying. The Telegraph has historically made cancellation harder to find, and you can see the tension between the short-term churn number and the long-term trust cost playing out in the design. Designers working on UK digital subscription UX design in 2026 need to understand that friction-as-dark-pattern is a diminishing returns strategy. Readers talk. Screenshots spread. The reputational hit from a genuinely hostile cancel flow now outweighs the churn it prevents.

    The newsletter as a retention surface, not a marketing tool

    One pattern I find genuinely clever is how The Guardian and The Times have repositioned their newsletters as subscriber benefits rather than acquisition tools. The distinction matters in design terms. A marketing newsletter is designed to drive a click back to the website. A subscriber benefit newsletter is designed to complete the reader’s experience of a topic without necessarily requiring a click at all. The typography, the information density, the presence or absence of advertising, all of these differ between the two modes.

    For product designers, this is a prompt to think about how print-to-web design systems handle brand identity across formats when the same content organisation is producing editorial for multiple surfaces simultaneously. A newsletter that feels typographically coherent with the desktop reading experience is reinforcing brand investment. A newsletter that looks like it was knocked together in Mailchimp in 2019 is quietly eroding it.

    What product designers outside media can actually take from this

    The reason UK digital subscription UX design in the media sector is worth studying is that publishers are running these experiments at massive scale with genuinely impatient users. The Guardian has millions of readers. The Times has a base built on premium expectation. The data behind every micro-decision in these flows is enormous. That makes their patterns a reasonable proxy for what works in subscription UX more broadly.

    A few things I’d take directly into non-media product work. First, visible scarcity is powerful but only when it is honest. Fake countdown timers are a dark pattern; a real article meter is a design feature. Second, the account dashboard is not administrative overhead, it is a retention surface that deserves the same design attention as the acquisition flow. Third, design systems that span multiple content surfaces, whether email, app, or web, need a coherent typographic and colour token layer underneath them. I’ve written before about building a design token pipeline that can scale across surfaces, and honestly, it is the kind of infrastructure investment that pays back every time you add a new subscriber touchpoint.

    British newspapers got disrupted slowly and then very fast. The UX decisions they are making now to claw back subscription revenue are some of the most consequential product design work happening in the UK. That is worth paying close attention to, even if your product has nothing to do with journalism.

    Frequently Asked Questions

    What makes UK digital subscription UX design different from standard SaaS onboarding?

    Media subscriptions involve a fundamentally different value proposition: the user already consumes the product before paying, so the paywall moment carries more psychological weight than a free trial expiry. The design challenge is converting habitual readers into willing payers without breaking the trust built through that free consumption, which is a harder ask than converting a trialist who signed up expecting to pay.

    How do The Guardian and The Times differ in their paywall approach?

    The Times uses a metered hard paywall with a visible article counter, creating a scarcity mechanic that drives conversion through loss aversion. The Guardian runs a contribution model with no hard cut-off, using persistent but low-aggression prompts that keep content accessible while building long-term trust. Both approaches reflect deliberate UX strategy rather than technical limitation.

    Are hard-to-cancel subscription flows legal in the UK?

    UK consumer protection law, specifically the Consumer Rights Act 2015 and CMA guidance on subscription traps, sets clear expectations around transparent cancellation. Subscription services that bury cancellation options or use misleading retention flows face regulatory scrutiny, and several UK businesses have received CMA attention on exactly these practices in recent years.

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

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

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

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

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

    What CSS scroll-driven animations actually do

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

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

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

    Browser support in 2026: the honest picture

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

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

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

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

    How to write it safely with @supports

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

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

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

    The performance case, and where it breaks down

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

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

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

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

    Accessibility: the part most tutorials skip

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

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

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

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

    Practical uses worth shipping right now

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

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

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

  • Building a Design Token Pipeline With Style Dictionary: A UK Agency’s Practical Walkthrough

    Building a Design Token Pipeline With Style Dictionary: A UK Agency’s Practical Walkthrough

    Managing brand tokens across six client accounts simultaneously is, to put it plainly, a nightmare without the right tooling. Colours drift between Figma files and code, spacing scales get hard-coded into components by a developer who never saw the original spec, and then a rebrand lands and you’re hunting through three separate codebases trying to find every instance of #1A73E8. I’ve been there. Most UK agency frontend teams have. The fix, as of 2026, is a properly configured Style Dictionary pipeline, and this walkthrough covers exactly how to set one up in a way that actually survives contact with real client work.

    Developer building a Style Dictionary design token pipeline at a dual-monitor desk setup
    Photo by Daniil Komov on Pexels

    What Style Dictionary actually is (and why it’s not just a JSON file)

    Style Dictionary, built by Amazon and maintained as an open-source project, is a build system that takes your design tokens, defined as structured JSON or JavaScript objects, and transforms them into whatever output format a given platform needs. That means CSS custom properties for your web builds, Swift variables for an iOS team, XML for Android, Sass maps if your project is still on that, and so on. All from a single source of truth.

    The critical word there is transform. Style Dictionary’s real power isn’t storage, it’s the transform pipeline. You define what your tokens mean at an abstract level, then the tool handles the mechanical conversion to whatever format a developer, app team, or platform actually consumes. For a UK agency juggling, say, a fintech rebrand alongside a heritage retail brand and a B2B SaaS product, this is the difference between a design system that scales and one that quietly collapses under the weight of one-off overrides.

    Structuring your token files for multi-client agency work

    The folder structure is where most teams go wrong out of the gate. The temptation is one flat JSON file per client. Resist that.

    A structure I’ve settled on, after several iterations across agency projects, looks like this:

    tokens/
      global/
        color.json
        spacing.json
        typography.json
      clients/
        acme-corp/
          color.json
          brand.json
        northfield-retail/
          color.json
          brand.json
    config.js

    The global layer holds your primitive tokens, raw values like blue-500: #2563EB or space-4: 16px, that have no semantic meaning yet. Client folders hold semantic tokens that reference those primitives: color.brand.primary: {value: "{color.blue-500}"}. This layering means that when Acme Corp decides their primary colour is now teal, you change one line in one file. Nothing downstream breaks unless you want it to.

    Style Dictionary’s reference syntax, the curly-brace notation, handles that aliasing natively. It resolves chains of references, so button.background can point to color.brand.primary which points to color.blue-500, and the final build output will resolve all the way down to the hex value. Tidy.

    Screen showing a Style Dictionary design token colour system with semantic naming
    Photo by cottonbro studio on Pexels

    Configuring the build for Figma handoff and CSS output

    The config.js file is where you wire everything together. Here’s a stripped-down but real example for a web-first client project:

    module.exports = {
      source: [
        'tokens/global/**/*.json',
        'tokens/clients/acme-corp/**/*.json'
      ],
      platforms: {
        css: {
          transformGroup: 'css',
          prefix: 'acme',
          buildPath: 'dist/acme-corp/',
          files: [{
            destination: 'tokens.css',
            format: 'css/variables'
          }]
        }
      }
    };

    Running style-dictionary build against this spits out a CSS file full of custom properties prefixed with --acme-, which you then import into your component library or bundle. Dead simple on the surface, but that prefix is crucial in an agency context because you can import multiple clients’ token files in a demo or shared environment without collisions.

    For Figma specifically, the handoff story has improved considerably in 2026. The Figma Tokens plugin and its successor integrations can read from a GitHub-hosted JSON file, meaning your single source of truth in the repo stays synchronised with what designers see in Figma. When a designer updates a spacing value in Figma’s token panel, a pull request is opened. When a developer merges it, the CSS rebuild runs. That’s the loop you want. I’d strongly recommend reading up on the current state of Figma’s Dev Mode before setting this up, because how your tokens surface in Dev Mode inspects has changed meaningfully this year.

    Custom transforms for UK-specific needs

    Out of the box, Style Dictionary’s CSS transform group handles most scenarios. But UK agency work throws up a few edge cases worth building custom transforms for.

    Fluid typography is a common one. If you’re using clamp() values for your type scale (which you should be, if you aren’t already), you’ll want those as computed strings rather than raw pixel values. You can register a custom transform that takes a min, max, and viewport range from a token’s attributes and outputs a proper clamp() string. Something like:

    StyleDictionary.registerTransform({
      name: 'size/fluid',
      type: 'value',
      matcher: token => token.attributes.fluid,
      transformer: token => {
        const { min, max, minVw, maxVw } = token.original.attributes;
        return `clamp(${min}px, calc(${min}px + (${max - min}) * ((100vw - ${minVw}px) / (${maxVw - minVw}))), ${max}px)`;
      }
    });

    That’s one registration, and every fluid type token across every client build uses it consistently. This kind of thing compounds nicely, especially once you have four or five custom transforms covering your agency’s standard patterns.

    Colour token naming is another area where getting the structure right early pays off enormously. If you’ve read our piece on colour token naming and contrast grids, you’ll know the semantic layer matters more than the primitive layer for day-to-day developer use. Style Dictionary makes enforcing that semantic naming convention in your JSON schema straightforward, because the dot-notation in your token paths maps directly to CSS custom property names.

    Running this across multiple clients without losing your mind

    The multi-client setup needs a build script, not just a single Style Dictionary config. I use a small Node script that iterates over a clients/ directory, dynamically generates a config for each client by merging global tokens with client-specific overrides, and runs a build. The output is a dist/ folder with one subdirectory per client, each containing their own token CSS file, ready to be consumed by whatever frontend stack that project runs on.

    This scales. I’ve run this against eight simultaneous client configs in under four seconds on a modest laptop. For a UK agency doing quarterly brand refreshes and annual rebrands, that turnaround matters.

    Version control is non-negotiable here. Every token change goes through Git, with a meaningful commit message, so you have a full audit trail of when a colour changed and why. This is useful not just for debugging but for client conversations: “your brand primary changed on 14 March, here’s the commit.” Clients, especially those in regulated industries, appreciate that level of traceability.

    For a sense of the broader landscape that UK product teams are operating in, the government accessibility requirements for websites and apps are worth keeping in your peripheral vision, particularly when your token system needs to account for WCAG contrast compliance across multiple brand palettes.

    Connecting your token system to the wider product workflow

    A token pipeline doesn’t live in isolation. It touches your component library, your Storybook instance, your CI build, and your Figma workflow. Getting those connections right is as important as the Style Dictionary config itself.

    One pattern I’ve seen work well at UK agencies is hooking the Style Dictionary build into a GitHub Action that fires on any change to the tokens/ directory. The action builds the tokens, commits the updated CSS to a dist branch, and optionally publishes to npm if the component library consumes tokens as a package. The whole thing runs in under thirty seconds. Developers get updated tokens without touching the config themselves, and designers don’t need to manually sync anything.

    If your agency also runs paid media for clients alongside the design work, the same discipline around structured, auditable systems applies. Agencies like dijitul Marketing bring that kind of systematic rigour to campaign management, which is a useful parallel: structured inputs, predictable outputs, traceable decisions.

    The token system also connects naturally to your test data story. If you’re prototyping UI states with realistic data, as covered in our walkthrough on using Faker.js and seed scripts to replace lorem ipsum, having your design tokens already resolved means your component demos use real brand colours and spacing, not placeholder guesses. The whole system starts to feel coherent rather than cobbled together.

    The things that will still trip you up

    A few honest gotchas, because no walkthrough should pretend this is frictionless.

    First, circular references. If token A references token B which references token A, Style Dictionary will throw an error at build time. That’s the right behaviour, but it means you need to be disciplined about your token graph. Primitives never reference semantics. Semantics reference primitives. Components reference semantics. One direction only.

    Second, token naming conventions need team buy-in. If one designer names something color.ui.button.primary and another names it button.primary.color, your JSON will become incoherent fast. Document your naming convention in your repo’s README on day one. Enforce it in code review.

    Third, the Figma plugin ecosystem for token sync is still more fragmented than it should be in 2026. Test your chosen plugin against a real client file before committing to it as your agency standard. Some plugins handle nested token groups cleanly; others flatten everything and lose the hierarchy that Style Dictionary depends on.

    None of these are deal-breakers. They’re friction points that good setup decisions eliminate early. Get the folder structure right, document the naming convention, and the pipeline genuinely does become the boring, reliable infrastructure it should be.

    Frequently Asked Questions

    What is Style Dictionary and how does it work with design tokens?

    Style Dictionary is an open-source build system that takes design tokens defined as JSON and transforms them into platform-specific outputs, such as CSS custom properties, Swift variables, or Sass maps. You define tokens once and the tool handles the conversion, meaning one source of truth serves every platform your product ships on.

    How do I manage multiple client brand systems with a single Style Dictionary setup?

    The recommended approach is a layered folder structure: a global layer for primitive tokens (raw colour values, spacing scales) and per-client folders for semantic tokens that reference those primitives. A build script iterates over client directories and generates a separate token output for each, so there’s no collision between brand systems.

    Can Style Dictionary output tokens that Figma can read?

    Yes. Style Dictionary’s JSON output can be consumed by Figma token plugins, with several integrations supporting a GitHub-hosted JSON file as the sync source. When a designer updates a token in Figma, the change opens a pull request; when merged, the Style Dictionary build runs and updates the CSS. It’s not entirely frictionless yet, but it works reliably for most agency setups.

  • Edge Functions for Frontend Designers: What Cloudflare Workers and Vercel Edge Actually Mean for UK Web Performance

    Edge Functions for Frontend Designers: What Cloudflare Workers and Vercel Edge Actually Mean for UK Web Performance

    Edge computing sounds like something a backend engineer should explain to you whilst you nod politely and think about font weights. I get it. The infrastructure layer has historically been not-your-problem, and for a long time that was fine. But edge functions have crossed a line. They are now genuinely useful to frontend designers and developers in ways that directly affect the things you spend your days obsessing over: page speed, personalisation, A/B testing, and whether your LCP score makes you want to cry.

    This is my attempt to explain edge functions as a frontend concern, not an ops concern, with real-world patterns UK teams are actually shipping in 2026.

    Frontend developer writing edge functions for performance improvements on a dark-themed code editor
    Photo by Daniil Komov on Pexels

    What edge functions actually are (without the waffle)

    A traditional web request goes: user’s browser, CDN cache, your origin server somewhere in a data centre. The origin might be in London if you’re lucky, Dublin if you’re using AWS eu-west-1, or somewhere entirely unhelpful if you never thought about it. Every uncached request hits that server, adds latency, and makes your Time to First Byte worse.

    Edge functions run code at CDN nodes, physically close to the user. Cloudflare has over 300 data centre locations globally. Vercel’s edge network is similar in concept. When a request comes in, instead of bouncing all the way to your origin, a small piece of JavaScript (or TypeScript) runs at the nearest node and returns a response. We’re talking single-digit millisecond execution in many cases.

    The key constraint worth knowing: edge functions are not Node.js. They run in a V8 isolate environment, which means no file system access, limited APIs, and very short execution windows. That constraint is actually what makes them useful for the frontend layer, because it forces you to keep edge logic small and sharp.

    Why this matters for edge functions frontend performance UK specifically

    UK users suffer more latency than they should from services hosted without European region configuration. If your Next.js app’s API routes are running serverless functions in us-east-1 because nobody changed the default, every dynamic request is crossing the Atlantic. Vercel’s edge runtime and Cloudflare Workers both put execution closer to the UK, and the difference in TTFB can be 200-400ms on uncached requests. That is not trivial when UK Core Web Vitals data shows that LCP and INP failures correlate directly with conversion drops on British e-commerce and SaaS sites.

    The Cloudflare network has a major presence in London (Docklands), Manchester, and Edinburgh. Vercel routes UK traffic through Frankfurt and Amsterdam primarily, with London capacity added in 2024. Either way, you are getting dramatically closer to your users than a single-region origin server.

    Three concrete things you can do with edge functions without a backend engineer

    1. Personalisation without client-side flicker

    The standard approach to personalisation in a React or Next.js app is: render the generic page, load JavaScript, check a cookie or API call, then swap in the personalised content. Users see a flash of the wrong content. It is grim, and it tanks your LCP because the meaningful content arrives late.

    Edge functions fix this by reading cookies and headers before the HTML is assembled. A Vercel Edge Middleware file (literally middleware.ts in your project root) can inspect the incoming request, check a user segment cookie, and rewrite the URL to a different version of the page before the browser sees anything. The user gets personalised HTML on the first response. No flicker, no extra round trip, no bespoke backend logic required.

    A practical example: a UK SaaS product serving both SME and enterprise tiers can serve entirely different homepage layouts based on a cookie set at signup, handled entirely in around 20 lines of TypeScript at the edge. No server, no database call, no delay.

    2. A/B testing that doesn’t torch your Core Web Vitals

    Client-side A/B testing with tools like Google Optimize (now retired) or its replacements typically inject test logic after the page loads. The result is layout shift, delayed LCP, and Cumulative Layout Shift scores that your performance audit will flag immediately. Edge-based A/B testing sidesteps this entirely.

    The pattern is straightforward. Your edge function generates or reads a test bucket (usually a random assignment stored in a cookie), then serves bucket A or bucket B of your HTML before the response leaves the CDN. From the browser’s perspective, there is only ever one version of the page. The Cloudflare Workers documentation has solid examples of this pattern using the HTMLRewriter API, which lets you modify HTML at the edge without reconstructing the entire response.

    I’ve used this pattern on a Next.js project for a UK fintech startup. We ran a button colour test (yes, really, the client insisted) across 50,000 monthly visitors without touching the origin deployment or writing a single line of backend code. The Lighthouse scores stayed identical between variants because the HTML was identical in structure, just different in content.

    3. Geolocation-based content without a postcode lookup API

    Cloudflare Workers and Vercel Edge both expose request geolocation headers automatically. cf-ipcountry in Cloudflare, and x-vercel-ip-country and x-vercel-ip-city in Vercel. You can use these to show UK-specific pricing (£ GBP, VAT-inclusive), route Scottish users to Scottish Gaelic content variants, or surface region-relevant promotions, all without a database call or a paid IP geolocation API.

    This is particularly useful for UK e-commerce sites that need to show duty-included pricing to GB users whilst showing export pricing to EU visitors post-Brexit. Handling that at the edge means the correct price is in the HTML from the first byte. No price flicker, no client-side currency swap, no additional JavaScript bundle weight.

    The Vercel vs Cloudflare choice for UK frontend teams

    If you are already deploying on Vercel, the edge runtime is the path of least resistance. Your middleware.ts file is just part of the project, it deploys automatically, and the routing primitives are well-documented. The limitation is that Vercel’s edge functions are tightly coupled to their platform, so you are buying into that ecosystem.

    Cloudflare Workers are more portable and more powerful. You can run them in front of any origin, including a Vercel deployment, which is a combination I have seen UK agencies use to get the best of both. Workers also have access to Cloudflare’s KV store (a globally replicated key-value database) and Durable Objects for stateful logic, which opens up patterns that Vercel middleware simply cannot do.

    For a solo developer or small UK agency, Vercel’s free tier is generous enough for most projects. Cloudflare Workers’ free tier gives you 100,000 requests per day, which is reasonable for prototyping. The paid tiers for both are reasonable once you are shipping something with real traffic.

    What edge functions cannot do (and why you still need some backend)

    Edge functions are stateless and fast, which means they are the wrong tool for heavy computation, database writes, large payload processing, or anything requiring persistent connections. You still need an actual backend (or a serverless function in a proper runtime) for those things. The edge layer is the routing and transformation layer, not the application logic layer.

    I’d also point out that cold start times, whilst dramatically better than traditional serverless functions, are not zero. Vercel’s edge functions typically have sub-1ms cold starts. Cloudflare Workers are similar. But if your logic becomes complex enough, you will want to profile it. The edge environment rewards small, focused functions.

    One thing this connects to for design teams: if you are building a Micro-SaaS product as a solo developer and you have been putting off personalisation because it felt like infrastructure work, edge middleware genuinely removes that blocker. You do not need a dedicated backend engineer to implement sensible routing logic in 2026.

    Getting started without breaking anything

    My recommendation: start with a read-only middleware. Something that inspects a cookie or a header and adds it to a request header for downstream consumption. This is zero-risk, teaches you the API surface, and gives you something to show in a pull request. Once that is merged and boring, move to URL rewrites. Then cookie setting. Then A/B bucket assignment. Each step is independently reversible.

    The Vercel middleware documentation is genuinely good. The Cloudflare Workers quick-start is also excellent, and the playground at workers.cloudflare.com lets you write and test Workers in the browser with live request inspection. Both platforms have strong TypeScript support, which matters if you are working on a codebase where designer-developer handoff is already type-driven.

    Edge functions are not magic, but they are one of the more genuinely useful infrastructure concepts to land in the frontend toolkit in the last few years. The performance gains are real, the personalisation patterns are practical, and the barrier to entry is low enough that a frontend developer comfortable with TypeScript can be productive within an afternoon. That is a rare combination in infrastructure land.

  • Micro-SaaS Product Design on a Shoestring: How UK Solo Developers Are Shipping Polished UIs Without a Designer

    Micro-SaaS Product Design on a Shoestring: How UK Solo Developers Are Shipping Polished UIs Without a Designer

    There is a very specific kind of despair that hits at around 11pm on a Tuesday, when your micro-SaaS is functionally complete, your Stripe integration works, your auth is solid, and then you open the browser and look at the interface you have built. It looks like a university coursework submission from 2014. This is the micro-SaaS UI design problem, and if you are a UK solo developer, it is probably the gap between shipping and actually getting paying customers.

    I have been watching this space closely for the past couple of years, and what has genuinely changed is that the tooling has caught up. The combination of mature component libraries, properly designed token systems, and headless UI primitives means a solo developer who is willing to spend a focused weekend can produce an interface that looks completely credible. Not just “passable”. Actually good. Here is how UK indie developers are doing it in 2026.

    UK solo developer working on micro-SaaS UI design at a desk late at night
    Photo by Daniil Komov on Pexels

    Why most indie developer UIs fail before the first demo

    The problem is almost never technical. Solo developers who can ship a multi-tenant backend with row-level security in Supabase are not struggling because UI is hard. They are struggling because good UI design requires a set of decisions that compound: spacing scales, colour relationships, type hierarchies, interaction states. Miss one layer and the whole thing looks wrong in ways that are hard to diagnose.

    The instinct is to grab a component library and bolt it on. That works, up to a point. But unstyled Tailwind with default shadcn/ui components looks exactly like unstyled Tailwind with default shadcn/ui components. Every micro-SaaS on Product Hunt right now has the same slightly-too-grey sidebar, the same Inter font at 14px, the same ghost buttons that vanish on light backgrounds. The trick is not just picking a library. It is understanding the layer underneath it: design tokens.

    Design tokens: the thing most solo developers skip entirely

    A design token is just a named variable for a design decision. --color-surface-primary instead of #ffffff. --space-4 instead of 16px. That sounds trivial until you have to change your brand colour across 47 components, or you need to add a dark mode, or your first enterprise customer asks if they can have a white-labelled version with their own colours.

    The W3C Design Tokens Community Group has been working on a standardised token format, and tools like Style Dictionary (maintained by Amazon) have made it possible to define tokens once and compile them to CSS custom properties, Tailwind config, and even iOS/Android values simultaneously. For a solo developer, this is huge. You define your spacing, colour, and typography decisions in one JSON file and let the toolchain propagate them everywhere.

    I would recommend starting with a minimal token set: five or six semantic colour roles (surface, on-surface, primary, on-primary, border, error), a spacing scale based on a 4px base unit, and two or three type sizes. That is genuinely enough to make an interface feel coherent. Resist the urge to over-engineer it before you have paying customers. If you are interested in how professional UK design teams handle token naming at scale, the contrast grids and colour token naming patterns being adopted by UK product teams are worth reading before you set up your own system.

    Design token system and component library used for micro-SaaS UI design on a monitor
    Photo by Ann H on Pexels

    The component libraries UK indie developers are actually shipping with

    Here is what I am seeing in the wild, from conversations in the Indie Hackers UK community and from poking around the public repos of recently-launched British micro-SaaS products.

    shadcn/ui with Radix primitives

    This is the dominant stack right now, and for good reason. shadcn/ui is not a traditional component library you install as a dependency. You copy the components directly into your project, which means you own the code. Radix UI provides the accessible, unstyled primitives underneath: dialogs, dropdowns, tooltips, all with keyboard navigation and ARIA attributes handled correctly. You get the accessibility baseline for free and the styling is entirely yours to control via Tailwind and your token layer.

    The downside is that the default aesthetic is quite neutral. You have to actually make design decisions on top of it, which is the point, but it means you cannot just install it and call it done.

    Mantine

    Mantine is the alternative I keep recommending to solo developers who want more out of the box. It has 120-plus components, a built-in theming system that maps closely to design tokens, and genuinely good defaults. The dark mode support is excellent. It is also well-maintained, with the Mantine team pushing consistent releases. The trade-off is bundle size and the fact that you are more constrained by the library’s design decisions. For a solo developer shipping fast, that constraint is often a feature, not a bug.

    Headless UI (from Tailwind Labs)

    Headless UI is narrower in scope than the others but worth mentioning for React and Vue developers already deep in the Tailwind ecosystem. It covers the components where behaviour is the hard part: listboxes, comboboxes, disclosure panels, transitions. If you are building a data-heavy SaaS with custom filtering UI, it handles the interaction logic cleanly.

    Making it look less like a template

    This is where the gap between “functional UI” and “credible UI” actually lives, and it is almost entirely about typography and whitespace. I cannot stress this enough. The single highest-return intervention a solo developer can make is to slow down and actually think about their type scale.

    Use a proper modular scale. Tools like Utopia.fyi let you define fluid type and space scales in CSS that interpolate between a minimum and maximum viewport width. Pair this with a good font: Geist (from Vercel, free) is excellent for product interfaces. Inter is fine but it is everywhere. Instrument Sans has a slightly warmer personality that works well for B2C SaaS. If you want to dig into the open-source font pairings that UK designers are currently using to stand out, the combinations worth actually using in 2026 cover this in proper depth.

    Beyond typography, the most common UI failure I see in indie products is insufficient spacing. Developers add padding that feels “enough” but does not give elements room to breathe. Double your padding values everywhere, then pull back by 20 per cent. You will almost always end up in a better place.

    Handling the conversion layer: landing pages and onboarding

    The app interface is only half the problem. For micro-SaaS, the marketing site and the first-run onboarding experience are where most revenue is won or lost. UK solo developers are increasingly treating these as separate design problems with different tooling.

    For landing pages, Astro with a component library like Flowbite Blocks or a purchased Tailwind UI template is a common approach. The layout decisions that actually differentiate UK B2B SaaS homepages are worth reviewing before you settle on a structure, because the default hero-features-pricing layout is doing no one any favours.

    For onboarding, the principle is simpler: show value as fast as possible, and do not ask for information you do not need yet. Progressive disclosure is your friend. Build the empty states deliberately. An empty dashboard that says “No data yet” is a conversion killer. An empty state that shows a greyed-out example with a clear single action is not.

    Getting eyes on your work early

    One thing that consistently separates the UK indie developers who ship polished products from those who do not is that they get external feedback before they think they are ready. The GOV.UK Service Standard has a principle around this: show the thing, not the idea. It applies equally well to commercial micro-SaaS.

    Post to the Indie Hackers UK Slack, share on X (Twitter), get a friend who is not a developer to try the onboarding. When I have spoken to solo developers who built products that actually convert, virtually all of them describe a moment where someone pointed out something completely obvious that they had been blind to because they were too close to it.

    For getting distribution sorted once you have a product worth promoting, tools like LinkPress are the kind of thing solo developers should be aware of as part of their broader go-to-market approach.

    The GOV.UK Service Manual design guidance is also genuinely useful reading even for commercial products. The thinking behind how they approach interaction design and user research is applicable well beyond public sector work.

    A realistic timeline for a solo developer

    Here is my honest estimate of what is achievable. Set up your token system and base Tailwind config: three to four hours. Implement shadcn/ui or Mantine and style it to your tokens: one full day. Land a coherent marketing page: another day. That is a weekend, realistically a long one. What you ship at the end of that will not be award-winning, but it will be clean, consistent, and credible enough to convert genuinely interested users.

    The mistake is treating design as something that happens at the end. Wire it into your development workflow from day one. Define your tokens before you write your first component. Pick your library before you scaffold your first page. The compounding benefit is enormous.

    Micro-SaaS UI design is solvable for solo developers. The tooling exists. The open-source resources exist. What it requires is treating design decisions as engineering decisions: deliberate, early, and documented.

    Frequently Asked Questions

    What is the best component library for a UK solo developer building a micro-SaaS in 2026?

    shadcn/ui with Radix primitives is the most popular choice because you own the code and have full styling control via Tailwind. Mantine is a strong alternative if you want more pre-built components and a built-in theming system without as many configuration decisions upfront.

    Do I need to understand design tokens to ship a polished UI?

    Not deeply, but you need the basic concept. At minimum, define your semantic colour roles and spacing scale as CSS custom properties before you write a single component. This single step will make your entire interface more consistent and saves significant time later when you need dark mode or white-labelling.

    How long does it realistically take to go from rough UI to polished interface?

    For a focused solo developer working with a good component library and a pre-defined token system, a weekend (roughly 12 to 16 hours of deliberate work) is enough to produce a credible, consistent interface for a small micro-SaaS product. Marketing page included.

    Is Tailwind CSS enough on its own for a micro-SaaS UI?

    Tailwind alone is a low-level utility layer, not a design system. Without a component library on top and a deliberate set of design tokens underneath, you will end up with an inconsistent interface that requires a lot of duplicated decision-making. Combine it with shadcn/ui or Mantine for best results.

    How do UK indie developers handle accessibility in their UIs without a specialist?

    Radix UI primitives and Mantine both handle the most common accessibility requirements out of the box: keyboard navigation, ARIA attributes, focus management. You still need to check colour contrast against WCAG AA standards, which you can do with free tools like the WebAIM Contrast Checker, but the structural accessibility is largely handled by the library.

  • The Real Cost of Ignoring Web Performance: What UK Core Web Vitals Data Actually Tells Us in 2026

    The Real Cost of Ignoring Web Performance: What UK Core Web Vitals Data Actually Tells Us in 2026

    There’s a number sitting inside the Chrome UX Report right now that should make every British frontend developer uncomfortable. Across a sample of UK e-commerce domains analysed through the CrUX dashboard earlier this year, fewer than 55% passed the INP (Interaction to Next Paint) threshold of 200ms. For context, INP became a Core Web Vital in March 2024, replacing FID, and Google has had over two years to let the ranking signal bed in. If your UK Core Web Vitals scores are still red, that’s not a future problem. That’s money leaving your client’s business today.

    Frontend developer analysing UK Core Web Vitals data on performance dashboard monitors
    Photo by ThisIsEngineering on Pexels

    I’ve spent a fair amount of time digging through CrUX field data for UK-hosted sites across retail, SaaS, and publishing verticals. What I keep seeing is a gap between what developers think their site scores and what real users on real networks in Birmingham, Manchester, or rural Scotland are actually experiencing. Lab scores in Lighthouse look fine. Field data is a mess. The delta is always user diversity: 4G on an ageing Android handset, a slow broadband connection in a terraced house with three people streaming simultaneously. That’s your actual audience for a huge chunk of UK traffic.

    What the CrUX data actually shows for UK sites

    Google’s CrUX (Chrome User Experience Report) aggregates real-world performance data from opted-in Chrome users, which in the UK means a reasonably representative slice of actual browsing conditions. You can query it directly via BigQuery, or use the PageSpeed Insights API to pull field data for any URL. I’d recommend doing both.

    A few patterns I keep seeing in 2026 UK data specifically:

    INP is the problem nobody fixed. LCP (Largest Contentful Paint) has improved significantly since 2022 because lazy-loading images and server-side rendering became standard. But INP measures responsiveness across the full page lifetime, not just load. JavaScript-heavy React and Vue SPAs tend to hammer INP because long tasks on the main thread block the browser’s ability to respond to user input. A user clicking a filter on an e-commerce PLP (product listing page) and waiting 600ms for anything to happen is having a bad time, even if the page loaded quickly.

    CLS is still broken on a surprising number of UK news and publishing sites. Cumulative Layout Shift above 0.1 is the threshold, and ad-heavy editorial properties routinely fail it. Late-loading ad slots that push content down, cookie banners that reflow the page, and web fonts swapping on render are the usual culprits.

    LCP on UK SaaS dashboards is frequently image-free but still slow. When the LCP element is a large text block rendered by JavaScript rather than a server-side HTML string, you get LCP scores of 4-6 seconds on mid-range devices. This catches out a lot of React-heavy product teams who never questioned whether their marketing site needed a client-side router at all.

    What sluggish scores are doing to conversions

    The Portent research (now a few years old but consistently replicated) put the penalty at roughly a 4.42% drop in conversion rate per additional second of load time in the 0-5 second window. More recent data from Cloudflare and Google’s own web.dev case studies backs this up across verticals. For UK SaaS with a £50/month average contract value and 10,000 monthly visitors, shaving 1.5 seconds off LCP and bringing INP under 200ms can shift conversion rates by 1-2 percentage points. Run that maths. It’s not a small number.

    E-commerce is even more sensitive. Basket abandonment on mobile is already brutal in the UK, where consumers expect fast experiences after years of conditioning by Amazon and ASOS. A slow PDP (product detail page) on a 4G connection isn’t just annoying; it’s a user who went back to Google, clicked the next result, and bought elsewhere. You don’t see that in your analytics beyond an exit rate and a nagging feeling.

    Laptop screen showing UK Core Web Vitals analytics and conversion rate charts
    Photo by RDNE Stock project on Pexels

    How to actually fix INP on a JavaScript-heavy site

    INP is harder to fix than LCP because it’s not a single moment but a score built from all interactions across a session. Here’s where I’d start.

    Profile long tasks in Chrome DevTools. Open the Performance panel, record a session, and look for red bars in the main thread. Any task over 50ms is a long task; anything over 200ms is the INP killer. You’re looking for event handlers that do too much synchronous work, or third-party scripts that block the main thread during user interaction.

    Yield to the main thread deliberately. The scheduler.yield() API (now available in all modern Chromium-based browsers and with a polyfill for Firefox/Safari) lets you break up long tasks explicitly. Wrapping expensive state updates in scheduler.postTask() with a ‘user-blocking’ priority gives the browser space to respond to input before continuing the heavier computation.

    Audit your event listeners. A React component that fires a synchronous setState inside an onInput handler for a search field, triggering a full re-render of a large list on every keystroke, is a classic INP wrecker. Debounce it, or defer the render using useTransition to mark the update as non-urgent.

    Move work off the main thread. Web Workers are underused in frontend codebases, particularly in UK agency projects where the brief was always “just make it work.” Filtering large datasets, parsing JSON from APIs, or running any sort of calculation that doesn’t touch the DOM is a strong candidate for a Worker.

    LCP: the quick wins that are still being missed

    If your LCP element is a hero image (check using the Performance panel or PerformanceObserver in the browser console), preloading it is the single highest-ROI fix available. A <link rel='preload' as='image'> in the document head, combined with correctly sized srcset attributes and a format like AVIF or WebP, will knock seconds off LCP scores for image-led pages. I’ve seen UK retail sites drop from 4.8 seconds to under 2 seconds LCP on mobile just from this change alone.

    If the LCP element is text, the priority is Time to First Byte (TTFB). Slow server response times, which are common on shared hosting and poorly configured WordPress installs (still ubiquitous in UK small business e-commerce), cascade into every other metric. A CDN with edge caching for static assets and full-page HTML caching where possible is non-negotiable. Cloudflare’s free tier handles this adequately for most small UK sites.

    Font loading is the other LCP trap. Using font-display: swap is not a complete solution; it trades invisible text for layout shift. The better approach is to subset your font files aggressively (only the characters you actually use), preload the critical weights, and keep the number of font families to two at most. I’ve written before about how open source font pairing is an increasingly smart approach for UK designers, partly because the files tend to be smaller and better optimised than some commercial typeface packages.

    Tools worth adding to your workflow right now

    Beyond PageSpeed Insights, the CrUX API is queryable for free and returns 28-day rolling field data. Pair it with Treo’s CrUX visualiser or build your own dashboard; I pointed to a walkthrough of that kind of project in a previous article on building a personal design dashboard using Railway and the Notion API, which is worth reading if you’re the type who’d rather have the data in front of you constantly than check it manually.

    Web Vitals Chrome Extension gives you field and lab data side by side while you browse. It’s the fastest way to gut-check any URL during development. Combine it with Webpagetest.org (run a UK test node, Dulles is not your audience) for waterfall analysis.

    For security-conscious teams handling client sites, anything that touches production performance should be monitored consistently. A compromised script injected into your bundle will tank INP immediately; if you ever notice a sudden unexplained regression in your INP scores, check your third-party scripts first. Services like dijitul Support handle malware removal and script injection issues specifically, which is relevant given how often performance regressions turn out to have a security root cause.

    The uncomfortable truth about UK Core Web Vitals in 2026 is that the tooling to diagnose and fix these issues has never been better, the data has never been more accessible, and yet the median UK site is still underperforming on INP specifically. The gap isn’t knowledge; it’s prioritisation. Performance work doesn’t get a ticket raised because it rarely has a visible face. It shows up as a sluggish feeling, a higher bounce rate, a slightly lower conversion rate than last quarter. Put a number to it, surface it to whoever controls the roadmap, and suddenly it gets prioritised. The CrUX data is the number. Use it.

    If you work across design and build (which is most of us on smaller UK teams), performance is a design decision as much as a technical one. The number of image carousels, the weight of a custom typeface, the choice to render a menu client-side instead of server-side: all of it lands in the field data eventually. Understanding what constrained design systems like GOV.UK get right is partly an object lesson in how limiting your component palette keeps your bundle lean and your users happy.

    Frequently Asked Questions

    What are UK Core Web Vitals and why do they matter for my site?

    Core Web Vitals are three metrics Google uses to measure real-world user experience: LCP (loading), INP (interactivity), and CLS (visual stability). They matter because Google uses them as a ranking signal, and poor scores directly correlate with higher bounce rates and lower conversion rates on UK e-commerce and SaaS sites.

    How do I check my site's Core Web Vitals field data for UK users specifically?

    Use the PageSpeed Insights API or query the Chrome UX Report (CrUX) via Google BigQuery to get 28-day rolling field data. Run Webpagetest.org tests using a London or Manchester server node to simulate realistic UK network conditions rather than US-based test servers.

    What is INP and why is it harder to fix than LCP?

    INP (Interaction to Next Paint) measures how quickly a page responds to user input across the entire session, not just at load. It’s harder to fix than LCP because it depends on all JavaScript running on the main thread throughout the user’s visit, including third-party scripts, event handlers, and framework re-renders.

    How much can improving Core Web Vitals actually improve conversion rates?

    Research consistently shows a 4-5% drop in conversions per additional second of load time. Fixing LCP and INP on a UK SaaS product or retail site can shift conversion rates by 1-2 percentage points, which at meaningful traffic volumes represents significant revenue. Google’s own case studies document similar improvements across e-commerce clients.

  • Print-to-Web Design Systems: How UK Publishing Houses Are Finally Bridging Their Brand Identity Across Both Mediums

    Print-to-Web Design Systems: How UK Publishing Houses Are Finally Bridging Their Brand Identity Across Both Mediums

    For decades, British publishing houses ran two essentially separate design operations. The print team lived in InDesign, Quark, and a proprietary colour system calibrated to CMYK press output. The digital team worked in whatever flavour of the week the web side had adopted, arguing about hex values and spacing scales that bore no relation to the print grid. The result was brand identity that forked the moment it left the studio: the same masthead, visually inconsistent across a magazine rack and a mobile browser. A print to web design system for UK publishing is the thing that finally addresses this, and in 2026 a handful of serious British media brands are making genuine headway.

    Designer working on a print to web design system at a UK publishing studio desk with magazine and Figma open
    Photo by RDNE Stock project on Pexels

    What makes this interesting from a systems perspective is that the problem is not merely cosmetic. It is architectural. Print design operates on a fixed-canvas, output-specific model where colour is defined in CMYK or Pantone, type is set in points, and the grid is absolute. Web design is fluid, responsive, device-agnostic. You cannot simply export one into the other. The teams that are succeeding are the ones that have stopped trying to translate between the two worlds and started building a shared language above both of them.

    Token architecture as the common layer

    Design tokens are the real unlock here. The idea, borrowed from software engineering, is that you define your design decisions (colour, type scale, spacing, border radius) as named values in a format-agnostic source of truth, then compile those tokens into whatever output each platform needs. For a publisher, that means a single token file can generate CSS custom properties for the web team, SCSS variables for the app team, and a colour swatch library that InDesign can actually consume via a plugin like Chroma or the Pantone Connect bridge.

    I have been watching how the team at one of the UK’s larger regional newspaper groups approached this. They started with colour, which is always the politically charged one in editorial. The brand had a primary red that existed in three slightly different hex values across their digital properties, and a CMYK equivalent that had drifted from the RGB over years of unchecked conversion. The first task was not building a system; it was forensic brand archaeology. They audited every asset, established a canonical sRGB value, derived the closest CMYK equivalent that would not blow out on newsprint, and then stored both in a single token. That token became the source of truth. Everything downstream consumed it. The print team got their CMYK. The web team got their hex. Nobody had to argue any more.

    The InDesign-to-Figma handoff problem

    This is where most print to web design system UK publishing projects either stall or produce something that technically exists but nobody uses. Figma is now the default tool for digital product design in UK agencies and in-house teams. InDesign is still the default for editorial layout, and there is no clean bridge between them. The tooling just does not exist in the way people wish it did.

    The workarounds I have seen actually work tend to fall into one of two camps. The first is a Figma library that mirrors the brand’s InDesign paragraph styles as Figma text styles, and the InDesign colour swatches as Figma colour variables. It is manual to set up, and it requires a human guardian to keep the two in sync as the brand evolves. Not elegant, but it works. The second camp is using a token management tool such as Style Dictionary or Tokens Studio (formerly Figma Tokens) to sit in between, pushing updates bidirectionally. This requires a bit of engineering investment, but for a publisher with multiple digital products and a monthly print title, the maintenance saving over three years is considerable.

    Print colour swatches next to digital design tokens on screen, illustrating the print to web design system workflow for UK publishing
    Photo by Fiona Murray on Pexels

    What I find genuinely tricky is typography. Print editorial type is set in absolute point sizes against a fixed baseline grid, often at sizes that would be absurd on screen (a 7pt caption, for instance, reads fine in print and is invisible on a phone). Web type, especially with fluid typography using modern CSS techniques like clamp(), is inherently relative. The token layer helps here too, but you end up with two separate type-scale tokens that share naming conventions without sharing values, which is honestly probably the right answer even if it feels philosophically unsatisfying.

    Where the biggest friction still lives

    Talk to any design systems lead at a UK media brand and they will tell you the hardest part is not the tooling. It is organisational. Print production teams and digital product teams have different cadences, different vocabulary, and different relationships with “the brand”. Print designers think in issues and editions. Digital designers think in components and releases. Neither is wrong. They are optimised for their medium. The friction is that a unified design system asks both sides to participate in a shared governance process they did not sign up for and do not have time for.

    The publishers making the most progress have appointed someone whose explicit job is to sit at that intersection. Not a print art director moonlighting in Figma, and not a UX designer who vaguely appreciates print. Someone who understands both production realities and has the political capital to tell both sides when the system wins over personal preference. The GOV.UK Design System works partly because it has that governance model baked in from the start. Private sector publishers are learning the same lesson the hard way.

    There is also a component naming problem that does not get discussed enough. InDesign templates use names like “StandFirst A” and “Dropcap Variant 3”. Figma components use names like “Typography/Body/Regular/16” or “Card/Article/Horizontal”. When you try to create a shared vocabulary, you either confuse the print team or you force them to abandon twenty years of muscle memory. The best systems I have seen build a translation layer into the documentation, rather than forcing one naming convention on both sides.

    What smaller publishers can actually do right now

    Not every UK publisher has the budget of a Condé Nast or a Reach plc. If you are a regional magazine, a specialist trade title, or an independent with a small design team, the enterprise token pipeline is overkill. Here is my honest take on what is actually achievable.

    Start with colour and type. Define your brand colours in a shared spreadsheet (yes, a spreadsheet) with columns for the colour name, hex, RGB, CMYK, and Pantone. Make that document the single source of truth. Pin it in every Slack channel design touches. Then create a Figma library from it and an InDesign swatch file from it. That is your first token layer. It is not automated, but it is consistent, and consistency is the actual goal.

    For the web side of this, the lessons from building shared design systems translate well beyond publishing. Small business web design faces a similar challenge at a different scale: brand assets built for one context (usually print, signage, or a PDF brochure) need to translate into something that works on screen. UK firms like dijitul Web Design, which builds custom small business websites, ecommerce web design, and WordPress and Shopify sites from dijitulwebdesign.co.uk, see this constantly with clients who hand over a CMYK logo and a brand guidelines PDF written for print and expect a coherent website redesign. The token logic that publishers are adopting at scale applies at this level too: define your canonical colour values, establish your type scale, then generate the correct output for each medium rather than manually converting between them.

    The improvements to Figma’s Dev Mode have made the handoff from design to web development considerably less painful over the past year, which helps the digital side of any print to web design system UK publishing workflow. But Dev Mode does not solve the upstream problem of getting print assets into Figma cleanly in the first place. That is still largely a human problem requiring a human process.

    One thing worth flagging for publishers thinking about this: the UK Government Service Standard offers a useful model for thinking about design governance even if you are not a public sector organisation. Its insistence on documented decision-making, accessibility from the start, and consistent component libraries is exactly the discipline that print-heavy organisations need to impose on their digital output.

    The component library question also touches on accessibility in ways that print never had to care about. Web components need to meet WCAG 2.2 AA as a baseline. Print had no such mandate. When you merge the two traditions, the digital accessibility requirements should win every time a decision point arises. That means some print design conventions (low contrast decorative text, type reversed out of textured backgrounds) simply cannot make it into the shared system unchanged.

    The honest answer to where this is all heading: the tools will get better, the token formats will stabilise further (the Design Tokens Community Group W3C spec is slowly becoming something real), and the organisational resistance will gradually erode as the designers who grew up switching between Figma and InDesign reach senior roles. For now, the publishers making progress are doing it through good governance and disciplined token work, not through any single magic tool. That, I think, is exactly as it should be.

    The brand consistency wins are real when it lands. A reader who picks up the print edition and then opens the app should feel the same visual language working on them. That is not a trivial achievement when the two artefacts are produced by different teams, in different tools, on different timescales. It just requires more upfront system thinking than most publishers have historically been willing to do.

  • Building a Personal Design Dashboard With Notion API and Railway: A UK Freelancer’s Weekend Project Walkthrough

    Building a Personal Design Dashboard With Notion API and Railway: A UK Freelancer’s Weekend Project Walkthrough

    I built this on a rainy Saturday in February, mostly out of frustration with juggling four browser tabs to answer one client question. A Notion database for projects here, a spreadsheet for invoices in GBP over there, a folder structure that made sense three months ago and now just feels like archaeology. The result was a self-hosted Railway app that pulls everything through the Notion API into a single, genuinely useful dashboard. It took a weekend. Here’s exactly how I did it.

    Before we get into the code, a quick note on why Railway specifically. For UK-based freelancers who want a self-hosted feel without maintaining a VPS at 2am, Railway’s free tier (and its £5/month hobby plan) is a reasonable middle ground. You get persistent deploys, environment variables, and a Postgres instance if you need one. It’s not perfect, but it’s fast to set up and the deployment pipeline is git-push simple. If you want a deeper look at framework choices for self-hosted tooling, the open source alternatives UK indie developers are actually shipping with article on this blog covers the broader landscape well.

    Developer workspace with a Notion API dashboard for freelancers displayed on a laptop screen
    Photo by cottonbro studio on Pexels

    Setting up your Notion API integration

    Log into Notion, head to notion.so/my-integrations, and create a new integration. Give it a sensible name, something like “Freelance Dashboard”, and grab the Internal Integration Token. Store it somewhere safe. You’ll need it as an environment variable in Railway shortly.

    Now, the important bit that trips people up: Notion integrations don’t automatically have access to your databases. You have to share each database with the integration manually. Open the database you want to connect, click the three-dot menu in the top right, go to “Add connections”, and select your integration. Do this for every database you plan to query. I have three: one for client projects, one for invoices, and one for design asset pipelines.

    Your database IDs live in the URL. When you open a full-page database in Notion, the URL looks like notion.so/yourworkspace/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX?v=.... The 32-character string before the query parameter is your database ID. Copy these out and store them alongside your token.

    Structuring your Notion databases for the API

    The Notion API returns data in a fairly verbose JSON structure, so it pays to keep your databases clean before you start querying them. For the client projects database, I use these properties: Client Name (title), Status (select: Active, On Hold, Complete), Start Date (date), Deadline (date), Project Value (number, formatted as GBP), and Brief (rich text). The API will hand all of these back as typed objects, which makes parsing straightforward.

    For invoices, I track: Invoice Number (title), Client (relation to the projects database), Amount (number), VAT (number), Paid (checkbox), Issue Date (date), and Due Date (date). The relation property is particularly useful because it lets you join data across databases in your app layer without building a separate relational database. Notion is doing that join for you, and the API respects it.

    The asset pipeline database is simpler: Asset Name (title), Project (relation), Type (select: Logo, UI Kit, Illustration, Icon Set), Status (select: Draft, Review, Approved, Delivered), and File Link (URL).

    Node.js code for a Notion API dashboard for freelancers shown in a terminal and editor
    Photo by Daniil Komov on Pexels

    Building the Railway app: Node.js + Express

    I went with Node.js and Express here. Nothing exotic. Create a new project directory, run npm init -y, then install your dependencies:

    npm install express @notionhq/client dotenv

    The official @notionhq/client package handles all the auth headers and request formatting. Create a .env file at the root:

    NOTION_TOKEN=secret_yourTokenHere
    PROJECTS_DB_ID=yourProjectsDatabaseId
    INVOICES_DB_ID=yourInvoicesDatabaseId
    ASSETS_DB_ID=yourAssetsDatabaseId
    PORT=3000

    Now the core of the app. Create index.js:

    require('dotenv').config();
    const express = require('express');
    const { Client } = require('@notionhq/client');
    
    const app = express();
    const notion = new Client({ auth: process.env.NOTION_TOKEN });
    
    async function queryDatabase(databaseId, filter = {}) {
      const response = await notion.databases.query({
        database_id: databaseId,
        ...filter
      });
      return response.results;
    }
    
    app.get('/api/projects', async (req, res) => {
      try {
        const pages = await queryDatabase(process.env.PROJECTS_DB_ID);
        const projects = pages.map(page => ({
          id: page.id,
          client: page.properties['Client Name'].title[0]?.plain_text ?? 'Unnamed',
          status: page.properties['Status'].select?.name ?? 'Unknown',
          value: page.properties['Project Value'].number ?? 0,
          deadline: page.properties['Deadline'].date?.start ?? null
        }));
        res.json(projects);
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    });
    
    app.get('/api/invoices', async (req, res) => {
      try {
        const pages = await queryDatabase(process.env.INVOICES_DB_ID);
        const invoices = pages.map(page => ({
          id: page.id,
          invoiceNumber: page.properties['Invoice Number'].title[0]?.plain_text ?? '',
          amount: page.properties['Amount'].number ?? 0,
          vat: page.properties['VAT'].number ?? 0,
          paid: page.properties['Paid'].checkbox,
          dueDate: page.properties['Due Date'].date?.start ?? null
        }));
        res.json(invoices);
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    });
    
    app.get('/api/assets', async (req, res) => {
      try {
        const pages = await queryDatabase(process.env.ASSETS_DB_ID);
        const assets = pages.map(page => ({
          id: page.id,
          name: page.properties['Asset Name'].title[0]?.plain_text ?? '',
          type: page.properties['Type'].select?.name ?? 'Other',
          status: page.properties['Status'].select?.name ?? 'Draft'
        }));
        res.json(assets);
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    });
    
    app.listen(process.env.PORT, () => {
      console.log(`Dashboard API running on port ${process.env.PORT}`);
    });

    Three endpoints, each pulling from a separate Notion database and shaping the response into something clean for the frontend to consume. The optional chaining throughout (?. and ??) is doing a lot of heavy lifting here; Notion properties can be null in ways the documentation doesn’t always warn you about.

    Deploying to Railway

    Initialise a git repository in your project folder and push it to GitHub. Then go to Railway, create a new project, and select “Deploy from GitHub repo”. Point it at your repo. Railway will detect the Node.js project automatically.

    Add your environment variables under the project’s “Variables” tab. Everything from your .env file needs to live there; Railway injects them at runtime. Do not commit your .env file to GitHub. Add it to .gitignore before you push anything.

    Add a start script to your package.json:

    "scripts": {
      "start": "node index.js"
    }

    Railway picks this up automatically. Your first deploy will take about 90 seconds. After that, every git push triggers a new build. Railway gives you a public URL in the format yourproject.up.railway.app. Your three API endpoints are now live at that base URL.

    Building the frontend dashboard

    I kept the frontend deliberately simple: plain HTML, a tiny bit of vanilla JS, and the excellent charting libraries that UK SaaS teams are increasingly reaching for. I used Chart.js specifically for a revenue-by-month bar chart that pulls from the invoices endpoint and groups Amount + VAT totals by the month of the due date.

    The key financial summary card calculates outstanding invoices (where paid === false) and displays total outstanding in GBP. No currency conversion, no dollar signs, just a clean £${total.toFixed(2)} string. Small thing, but it matters when you’re reporting income to HMRC.

    Serve the static frontend from the same Express app using express.static('public') and drop your HTML, CSS, and JS files into a /public folder. One deploy, one URL, everything in one place.

    A few things I learnt the hard way

    Notion’s API rate limit is 3 requests per second. If you’re querying three databases on page load, stagger them or use Promise.all carefully. I cache responses for 60 seconds using a simple in-memory object to avoid hammering the API on every browser refresh.

    Relation properties in the Notion API return page IDs, not the actual related page data. If you want the client name in your invoice response, you need a second API call to fetch that related page. I chose to keep it denormalised in the invoices database instead by adding a plain text “Client Name” field that I fill manually. Not elegant, but it halves the API calls.

    Finally, if you’re dealing with sensitive client data in these databases, think carefully about what you expose through the Railway URL. I added a simple API key check middleware that validates a header against another environment variable. Nothing fancy, but enough to stop anyone who stumbles across the URL from reading your invoice totals. And speaking of due diligence with data, I was reminded recently whilst reviewing a client’s office refurbishment docs that the same careful approach applies to physical compliance paperwork; a colleague mentioned they’d used Asbestos Compliance Solutions for site documentation before any structural work began. Proper records, properly stored. Same principle, different domain.

    This whole setup is genuinely more useful than I expected. The Notion API is well-documented, Railway removes all the ops friction, and the result is a dashboard that actually reflects how I work rather than forcing me to adapt to someone else’s product. If you want to push it further, look at adding webhook listeners to Notion page updates, or wiring in a Telegram bot that pings you when an invoice goes overdue. The foundation is there. Now go build something on a weekend.

    Frequently Asked Questions

    Is Notion's API free to use for personal freelance projects?

    Yes, the Notion API is free to use and included with all Notion plans, including the free tier. You can make up to 3 requests per second, which is plenty for a personal dashboard querying a handful of databases.

    What does Railway cost for hosting a small Node.js app like this?

    Railway’s free tier gives you $5 of credit per month (applied to resource usage), which is usually enough for a low-traffic personal app. Their Hobby plan at around £5/month removes usage caps and adds persistent deployments with no idle spin-down, which is worth it for a dashboard you check daily.

    Can I track invoices in GBP specifically through the Notion API?

    Notion stores number properties without currency formatting at the data level, so your amounts are just numbers in the API response. You apply the GBP formatting in your frontend code using JavaScript’s Intl.NumberFormat with ‘en-GB’ locale and ‘GBP’ currency, which gives you properly formatted pound sterling strings.

  • 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.