058

Multi-Page SaaS Template

£20 or the whole Vault, £79.20

A four-page SaaS website template with animated page transitions and a real light/dark theme: links fetch the next page and swap only the main column, so the header, the mega menu and the theme survive every navigation. Re-points to a developer tool, an API product, an analytics or fintech SaaS, an agency site, or any multi-page marketing site.

ScrollTrigger Lenis advanced
15 more details
page-transitionsoft-navigationmega-menutheme-switchscrubcount-updrag-interactionkeyboard-navigationfocus-managementscroll-revealstaggerclick-togglescrollspyview-transitionsdirectional-transition

This template reads better at your own screen size than in the frame below:

Updated

About this template

Runwell is a complete four-page website for a software product, built around the thing a one-page template cannot do: moving between pages. Home, Product, Pricing and Changelog are four real HTML files, and every link between them is a real href, but a click never reloads the browser. The next page is fetched, only the main column is replaced, and the header, the open menu, the scroll engine and the theme you chose carry straight across.

Read the full template overview

The art direction is deliberately quiet. One pale eucalyptus ground, one near-black ink, one deep ink-blue accent used about once per screen, and hairlines instead of shadows. The home page opens on its headline set at display scale across the full width, with a dark band of figures beneath it, one of them live. The pricing page is built on a rail you drag: set your monthly volume and the plan that fits lights up, the price counts to its figure and the arithmetic is written out beneath it. The product page runs a small queue while you watch, jobs arriving and workers taking them.

The chrome is the part most product sites get wrong and it is built properly here. The mega menu is one panel that reshapes rather than reopening, and it is keyboard-routable rather than Tab-broken by DOM order. The theme toggle grows the new theme out of the button itself. On a phone, a drawer takes over with the same sections as accordions.

Making it yours is mostly renaming. The fictional product is a background job runner, but nothing in the structure assumes that. Repaint six values in the token block and their six dark counterparts, swap the copy and the plan figures, and add a page by copying one of the four files: the router picks it up with no configuration, because it works from ordinary links.

Multi-Page SaaS Template - website template preview

What's included

10 items
  • Animated page transitions between four real HTML pages: the column clears and rewrites like log lines, a job chip shows the request queued, running and done with the real milliseconds it took, and each destination plays its own entrance; only the main column is swapped, so the header, menu, theme and smooth scroll persist
  • A real light and dark theme, applied before first paint so the page never flashes the wrong one, defaulting to the visitor's system preference and remembered between visits
  • A theme toggle that opens the new theme out of the button as a growing circle, so text and rules change under the moving edge while the page keeps scrolling and ticking; one mechanism in every browser
  • A pricing rail you drag: monthly volume on a log scale from 10,000 to 50 million runs, the plan that fits lit on the rail and in the comparison table, the price counting to its figure, the arithmetic written out, and the three plans re-weighting as the fit changes; arrow keys, Home, End and Page keys move it too
  • A home hero set as type at display scale across the full width, with a dark band of figures beneath it and the first figure ticking up live while it is on screen
  • A scroll-scrubbed run lane: a playhead sweeps a batch of jobs, each bar fills as it is crossed, durations print when they land, and the completed counter climbs
  • A driven queue on the product page: jobs arrive, three workers take the oldest, hold it and release it, running only while the block is on screen
  • A mega menu that reshapes rather than reopening, with full keyboard routing, Escape handling and a focus-out close
  • A mobile drawer with focus trapping and the mega menu's sections as animated accordions
  • Every page works with JavaScript disabled and with the animation CDN blocked: links are real hrefs, the menus fall back to plain DOM, and the router simply never engages

Perfect for

5 use cases
  • Any software product that needs more than a landing page: a home page, a feature page, pricing and a changelog
  • Developer tools, APIs and infrastructure products where the buyer expects a docs-shaped site with a dark mode
  • Analytics, fintech and usage-priced SaaS, where a pricing rail says more than three static cards
  • Agencies and freelancers who want a multi-page chassis with the navigation and transitions already solved
  • Any site where a light and dark theme is a requirement rather than a nice-to-have

How it works

5 sections

Two layers, one script

The page is split into chrome and page. The chrome (header, mega menu, drawer, theme toggle, router and smooth scroll) is built once when the site first loads and is never torn down. Everything inside the main column lives in its own setup and teardown path, rebuilt on each navigation so nothing from the page you left, including its timers, leaks into the one you arrived on.

The page transition

A click on a same-origin link is intercepted and the destination is fetched while the current page clears: each block of the column leaves in the direction of travel like lines being cleared from a log. A small job chip under the header shows the request queued, then running with a progress rule, then done with the real time the navigation took, measured from the click to the new page being on screen. The new page writes in block by block, then plays its own entrance: the home page's headline rises through its masks and the band slides up beneath it, the product page brings features in from the side each one sits on, the pricing page draws the rail out and deals its plans, and the changelog slides entries in with their oversized version numerals following. In-page links scroll to their target under the sticky header rather than jumping.

The pricing rail

The three plan cards carry their own numbers: the runs included, the monthly base and the rate per 10,000 runs over. The rail reads those and nothing else, works out the cheapest plan for the volume under the knob, and lays out the tier zones on the track from the same arithmetic, so changing a price in the markup changes the whole page. The knob is a real slider for assistive technology, the price counts to its figure, and a release carries a little of the drag's speed before settling.

The theme switch

The stored choice is applied by a synchronous script in the head, before first paint, so the page never flashes the wrong theme. Changing it opens the new theme from the toggle as a growing ring: outside the ring the page you were looking at stays exactly as it was, and every line of text, rule and band changes the moment the edge reaches it. Smooth scrolling pauses for the two thirds of a second the ring takes and picks up where it left off, so nothing jumps when it finishes. Browsers without the API used for that get a cover in the old ground that opens the same way. Under reduced motion the theme simply changes.

Without JavaScript or motion

Every navigation link is a real href, including the mega menu triggers, so the site is an ordinary four-page website when the router never runs. The menus close as display:none rather than hidden layers, so nothing is present but unreachable. If the animation CDN is blocked the script removes the class gating the reveals and leaves a complete, readable document. Under reduced motion the transitions become instant swaps, the run lane renders as a finished batch, the queue is a still of a busy queue, and the rail moves without easing.

Plugins ScrollTrigger
Difficulty Advanced
Smooth scroll Lenis integration
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this template's demo (4 pages, lowest score shown), from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Questions about this template

The demo is a background job runner. What else does this design suit?

Anything sold on a website with more than one page: a developer tool, an API product, an analytics or fintech SaaS, an agency with a home, work, pricing and news page, or any multi-page marketing site. The vertical is carried by words and by the plan figures. Nothing in the layout assumes jobs or queues, so it re-points by replacing copy, code samples and prices.

Do the animated page transitions work on ordinary web hosting?

Yes, on any static host: Netlify, Vercel, Cloudflare Pages, GitHub Pages or plain shared hosting. The pages need to be served over http or https rather than opened straight off a hard drive, because each one is quietly fetched before it swaps in. Opened from a file, the links just navigate the normal way and nothing is broken.

Can I change the plans and prices on the pricing rail?

Yes, in the markup alone. Each plan card carries three numbers: the runs it includes, its monthly price and its rate per 10,000 runs over. Edit those and the rail, the tier zones, the price readout and the arithmetic line all follow. Add a fourth plan by copying a card; the rail works out where it becomes the cheapest. Rename 'runs' to whatever you meter, seats or requests or gigabytes.

Can customers sign up or pay through it?

No. This is a front-end template with no backend: Start free and Talk to us are links, and there is no signup form, no account area and no payment flow. Point the buttons at your real signup page, or at a Stripe payment link, and the site is doing its job.

Does the light and dark switch actually stay switched?

Yes. The choice is saved in the browser and applied by a script in the page head before anything is drawn, so a returning visitor never sees a flash of the wrong theme. Someone arriving for the first time gets whichever their operating system is set to. To ship light only, delete the dark block and the toggle.

How do I add a fifth page?

Copy any of the four files, change the content inside the main column, and add a link to it in the header and footer. The router works from ordinary links rather than a route table, so it picks the new page up with no registration step. Keep the header and footer markup identical across pages so the chrome stays in sync.

Will the mega menu work on a phone?

The mega menu is desktop only, which is deliberate: a hover flyout is the wrong shape for a touch screen. Below 900px a drawer takes over and the same sections appear as tap-to-open accordions. The pricing rail is dragged by a finger on a phone, and a tap on the track jumps the knob there.

Paid template

Purchase to unlock the code.

Buying Multi-Page SaaS Template opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

£20 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £79.20, one payment.

Documentation

Quick Start

No build step, no framework, no configuration. Open index.html.

To deploy, upload the whole folder to any static host (Netlify, Vercel, Cloudflare Pages, GitHub Pages, or plain shared hosting). The page transitions need the pages to be served over http:// or https:// rather than opened from file://, because they are fetched: everything else works either way, and from file:// the links simply navigate normally.

Code snippet omitted: it ships with the download.

The two layers

This is the one idea worth understanding before you edit anything.

Chrome is the header, mega menu, drawer, theme toggle, router and Lenis. The router shows every navigation as a run: the job chip ([data-route-job]) under the header reads queued, running and done with the real time the navigation took, and the column clears and rewrites block by block. In-page links (product.html#retries) are scrolled by the script under the sticky header, on the current page and after a navigation. Back and Forward return each page to the scroll position you left it at. Links prefetch their page on hover, focus and touch, so the fetch has usually finished before the click. The latest click wins: a second link chosen while the column is clearing goes straight to that page, and one chosen while a page is arriving lets it land quickly and then moves on. Back or Forward mid-transition settles on the page the browser asked for, and if a page cannot be fetched the link loads it normally. It is built once when the site first loads and never torn down. That is why the theme you chose and the menu you opened survive a navigation.

Page is everything inside <main>. It lives in its own gsap.context, which is reverted and rebuilt every time the router swaps a page in, so ScrollTriggers from the page you left cannot leak into the one you arrived on.

If you add an animation that belongs to a page, put its init inside mountPage(). If it belongs to the site, put it in the boot block at the bottom of script.js. Scripts inside a fetched page never run, so new behaviour always goes in script.js.

The transition itself is the leave, hold and enter functions passed to PageRouter.create in startRouter(); the header comment of assets/page-router.js lists everything they receive. Every page loads assets/page-router.js before assets/script.js; keep that order on a new page.

Each page arrives in its own way. The shared part is the column sliding in from the direction you travelled, taken from the nav's own order. On top of that, entranceFor() in script.js plays a per-page timeline: the home page raises its headline through its masks and slides the band up beneath it, the product page brings features in from the side each one sits on, the pricing page draws the rail out and deals its plans, and the changelog slides entries in with the version numerals following. Add a branch there for a new page, or leave it out and it simply gets the shared column move.

Adding, removing and reordering pages

To add a page: copy any of the four files, replace the content inside <main>, and add a link to it in the header and the footer. There is no route table to register it in; the router works from ordinary links, so it is picked up as soon as one points at it.

Two rules keep it working:

  • Keep the header and footer markup identical on every page. Only <main> is swapped, so the chrome on screen is whichever page you loaded first. Anything you change in the header must be changed in all of them.
  • Give the new page <main id="main" data-page="your-page">. The router reads the <main> element and nothing else.

To make a nav link show as current, add data-nav-page="your-page.html" to it and aria-current="page" on the copy in that page's own header.

To remove a section, delete it. Every init in script.js returns early when its markup is absent, so nothing else breaks.

Sections and their options

Section Where Notes
Hero index.html Two headline lines at display scale, each in its own mask. The band beneath holds four figures; the one with data-live ticks up while on screen, from the number in that attribute at roughly data-live-rate a second.
Run lane index.html Each row's data-start / data-end (0-100) sets where it sits in the batch and when the scrub fills it. The inline --s / --w on .lane__bar are the same two numbers, so a no-JS visitor gets the same picture.
Ledger index.html A numbered list of steps with a monospace figure each. Add or remove rows freely.
Feature blocks product.html Three shapes: the default split, .feature--wide for a full-width block, and a split whose artifact is the backoff diagram. Odd and even blocks alternate sides automatically.
Queue product.html [data-queue]: jobs arrive in the lane and three workers take them while the block is on screen. The job names are the NAMES list in initQueue; the static chips and the data-busy workers in the markup are the still shown without JavaScript or under reduced motion.
Rail product.html Built from the id on each .feature. Add a section, add a rail item pointing at its id.
Usage rail pricing.html [data-usage]: a log-scale rail from 10,000 to 50,000,000 runs a month (RAIL_MIN / RAIL_MAX in script.js). The knob's data-value is its starting position as a share of the rail. Arrow keys step 2%, Shift 10%, Home and End go to the ends, Page keys jump between tiers.
Plans pricing.html Each .plan carries data-included, data-base and data-over (the rate per 10,000 runs over). The rail reads those and nothing else, so editing a price here changes the readout, the zones on the track and the column lit in the comparison table. The plan that fits gets data-fit="true".
FAQ pricing.html Buttons with data-disclosure and aria-controls.
Changelog entries changelog.html The oversized version numeral is aria-hidden, decorative, and repeats the version already set in the entry.

Theme

Two complete themes ship and both are the product, not a preview tool.

  • Light is the default, in :root. Dark restates every token under [data-theme="dark"].
  • A first-time visitor gets whatever their operating system asks for. Their choice is then stored in localStorage under runwell-theme.
  • The stored choice is applied by a synchronous script in <head>, before anything is painted. Keep it there and keep it synchronous, or returning visitors get a flash of the wrong theme on every page load.

Changing the theme opens the new one from the toggle as a growing ring. Outside the ring the page stays as it was; inside it everything is already repainted, so text, rules and the band change as the edge reaches them. Smooth scrolling pauses for the duration and resumes after, which is what stops the page jumping when the ring finishes. Browsers without the View Transitions API get a cover in the old ground that opens the same way, and reduced motion swaps the theme at once.

To ship light only: delete the [data-theme="dark"] block, the toggle button in the header, and the head script's theme lines.

Palette

Repaint the six solid values in :root and their six counterparts in [data-theme="dark"]. Everything else on the page is one of them at another opacity, and every translucent value resolves from a --*-rgb triple, so there are no rgba() literals to hunt down.

Contrast is measured, composited as used, in both themes, and the full table is in the :root header comment in style.css with a note on which pair has the least headroom. Two things to know before changing colours:

  • The accent has to swap direction between themes. A single blue cannot serve both grounds: the light accent measures 1.95 on the dark ground, and the dark accent measures 1.60 on the light one. Each theme carries the version that clears 4.5:1 on its own ground.
  • Solid accent fills always take --ground as their text colour, never --ink. That holds in both themes.

Accessibility

  • Colour pairs meet WCAG AA at the sizes they are used, in both themes.
  • The mega menu is keyboard operable: focus a trigger to open its panel, Down to enter it, Escape to close and return focus to the trigger. Focus landing anywhere else closes it. A shared flyout sits after every trigger in the DOM, so this is routed by hand rather than left to Tab order.
  • The drawer traps focus while open and returns it to the button on close.
  • After a page transition, focus moves to the new page's heading (or the section a link pointed at) and its title is announced in a live region.
  • Under prefers-reduced-motion the transitions become a short crossfade with no job chip, the run lane renders as a finished batch, and the theme change is an immediate repaint. Nothing is hidden and nothing needs an animation to be reached.
  • Every nav link is a real href, so the site is an ordinary four-page website with JavaScript disabled.

Without JavaScript, or with the CDN blocked

Both are supported and both were tested.

The reveal animations are gated by a has-js class added in <head>. If GSAP does not arrive, script.js removes that class and the page is a complete, readable document. The theme toggle and the menus still work, because they fall back to plain DOM; only the motion is lost. The router never engages, so links navigate normally.

The menus close as display: none rather than as hidden layers, so there is never content present but unreachable.

Making it yours

The vertical is carried entirely by words. To re-point it at your own product:

  1. Repaint the twelve values in the two token blocks.
  2. Replace the copy, the code samples on product.html, and the plan names and figures on pricing.html (the three data- numbers on each plan drive the rail; rename "runs" to whatever you meter).
  3. Replace the wordmark: the .brand SVG and the name beside it.
  4. Point Start free and Talk to us at your real signup page.

There is no backend. The buttons are links, and there is no signup form, account area or payment flow anywhere in the template.

Smooth scroll

Lenis is wired in and set up once, surviving every page navigation like the rest of the chrome. It is off automatically under prefers-reduced-motion.

To ship without it, delete the initLenis block at the bottom of script.js and the Lenis <script> tag from all four pages. Everything else is unaffected: the ScrollTriggers are correct either way.

Dependencies

GSAP 3.12+ with ScrollTrigger, and Lenis (optional). Both load from a CDN; the tags are at the bottom of each page. Built and tested against GSAP 3.15.0.

Previewing both themes

The native header theme toggle stays inside the site, including the embedded Vault preview. This template has no separate style controls above its preview. A link ending in ?palette=light or ?palette=dark opens that theme before first paint; subsequent theme changes update the selected value in that link. Without a query, the existing saved choice and system preference apply.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.