Pricing toggle
A pricing table of two, three or more plans with a monthly or yearly switch, and the switch is a crank.
Documentation
-
Copy to Webflow
Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: Webflow removes any font your site has not installed. -
Add custom CSS
Custom CSS in WebflowThis CSS already ships inside the Webflow embed, so a pasted section needs nothing added. Paste it yourself, in the page or the site custom code, only if the effect runs on several pages. The tutorial explains where.
See the tutorialThe look of the preview is thepricing-toggle-*classes: the switch, its knob and the two words inside it, the plates of the plans. What moves is in the Embed. Its accent is thecolorsetting; the ink of the focus ring and the ground a hovered button takes are two variables,--pricing-toggle-inkand--pricing-toggle-ground, to set on the section in a rule of your own. A price is kept on one line and asked for tabular figures, so nothing moves sideways while it turns. The switch is a Link block, because Webflow has no button element: the Embed keeps a click on it in place and lets Space press it. -
Add custom JS
Custom JavaScript in WebflowThis script already ships inside the Webflow embed, settings block included. You paste it yourself, before the closing body tag of the page or the site custom code, only when the effect has to run on several pages, and the tutorial says how to choose.
See the tutorial -
Tune it
How to add a custom attribute in WebflowA custom attribute is a pair, a name and a value, typed in the Settings panel of the element you selected, under Custom attributes. The name is what the script looks for, the value is what it reads. The tutorial shows the panel.
See the tutorialCustom attributesSelect the section on the canvas and open the Settings panel. Under Custom attributes, add a pair: the name on the left, the value on the right.Custom attribute What it does data-pricing-toggle Marks the block that holds the switch and the plans: the section of the paste, or any box of your own. It takes no value: leave the value field empty. The settings below go on the same element. data-pricing-toggle-switch On the switch, the element that is pressed and dragged. It takes no value. The engine gives it role="switch" when it has no role, writes aria-checked on it and names it after the word of the yearly side. Without it nothing can be pressed, and the console says so. [ Required ] data-pricing-toggle-price On each price, with its two values beside it: data-pricing-toggle-monthly="$49" and data-pricing-toggle-yearly="$490", written the way they should read, currency and separators included. The price rolls digit by digit. Left without data-pricing-toggle-monthly, the text of the element is the monthly value. The text is also what a page without JavaScript shows. [ Required ] data-pricing-toggle-yearly The yearly value. On a price it sits beside data-pricing-toggle-price. Alone on any other element, a word, a line, it gives that text its yearly version, "per year" for "per month", and the whole text turns as one. Without it on any element of the block, nothing changes with the switch. [ Required ] data-pricing-toggle-label On the visible word of each side, with the value monthly or yearly, inside the switch or beside it. A click on it sets that side, the one not chosen steps back, and the yearly one names the switch for a screen reader. [ Optional ] data-pricing-toggle-knob On the knob inside the switch. It takes no value. Without one the engine adds a span, which then needs a size and a colour of your own. [ Optional ] data-pricing-toggle-yearly-href On a link, with its address on the yearly side as the value, the yearly checkout page of the plan for one. Its href is the monthly one, unless data-pricing-toggle-monthly-href gives another. [ Optional ] data-pricing-toggle-show On what is there on one side only, with the value yearly or monthly: a note, a badge. It keeps its room on the other side, unseen, so nothing jumps. [ Optional ] data-pricing-toggle-plan On the box of a plan. Its heading, or the value of the attribute when it has one, is the name said before the price when the new prices are read to a screen reader. [ Optional ] data-pricing-toggle-cta On the button of a plan: the hover of the preview, the button takes the accent. With the value usual, for the button already in the accent, it takes the ground instead. [ Optional ] data-pricing-toggle-period Written by the engine on the block: monthly or yearly. Never written by hand; a style of your own can read it. [ Optional ] SettingsEvery setting is a custom attribute too, added the same way and on the same section. An attribute always wins over the defaults written in the code.Attribute Default What it is data-pricing-toggle-color #ffab1a The accent: the colour that fills the track of the switch as the knob reaches the yearly side, and the one a plan's button takes under the pointer. Options: any CSS colour. The engine writes it on the block as --pricing-toggle-color. data-pricing-toggle-duration 1.6 Seconds the prices take to cross from one side to the other after a click or a key, leaving slowly and settling slowly. A drag is not timed: the prices stay under the hand. data-pricing-toggle-spread 0.72 Share of the travel over which the columns leave one after the other, from 0 to 0.9. data-pricing-toggle-start monthly The period shown when the page arrives. Options: monthly or yearly. Without JavaScript the page shows the monthly side whatever this says. data-pricing-toggle-remember false Keeps the period the visitor chose, in the browser's local storage, and shows it on the next visit instead of start. Options: true or false. One choice for the whole site: every table that remembers reads the same one. data-pricing-toggle-drag true Whether the knob may be dragged. Options: true or false. At false the switch only answers a click and the keyboard, and the prices still follow the knob. data-pricing-toggle-announce true Says the new prices once to a screen reader after a change, as one sentence: the word of the side, then each plan and its price. Options: true or false. At false the switch still says its state and each price still reads as its current value.
Two files, no build step.
It runs in the browser only. Load it with a script tag in plain HTML, in a
client-side script in Astro, never in the frontmatter. In React, call
PricingToggle.mount(ref.current) in useEffect and
destroy() in its cleanup.
-
Tune it, either way
Per element with an attribute, or once for the whole site by editingDEFAULTSat the top of the file.Attribute Default What it is Lower Higher data-pricing-toggle-color #ffab1a The accent: the colour that fills the track of the switch as the knob reaches the yearly side, and the one a plan's button takes under the pointer. Options: any CSS colour. The engine writes it on the block as --pricing-toggle-color. data-pricing-toggle-duration 1.6 Seconds the prices take to cross from one side to the other after a click or a key, leaving slowly and settling slowly. A drag is not timed: the prices stay under the hand. A quicker roll; under 0.8 the wave from plan to plan is no longer read A slower one, more time on each figure; past 2.4 the last plan keeps the visitor waiting data-pricing-toggle-spread 0.72 Share of the travel over which the columns leave one after the other, from 0 to 0.9. The columns turn closer together; at 0 they all turn at once A longer wave across the table, each column turning faster inside it data-pricing-toggle-start monthly The period shown when the page arrives. Options: monthly or yearly. Without JavaScript the page shows the monthly side whatever this says. data-pricing-toggle-remember false Keeps the period the visitor chose, in the browser's local storage, and shows it on the next visit instead of start. Options: true or false. One choice for the whole site: every table that remembers reads the same one. data-pricing-toggle-drag true Whether the knob may be dragged. Options: true or false. At false the switch only answers a click and the keyboard, and the prices still follow the knob. data-pricing-toggle-announce true Says the new prices once to a screen reader after a change, as one sentence: the word of the side, then each plan and its price. Options: true or false. At false the switch still says its state and each price still reads as its current value. -
Drive it from JavaScript
When the period has to be set from code, a link that says "see yearly prices" for one, when the page reacts to the choice, or when plans arrive after the page has loaded.const element = document.querySelector('[data-pricing-toggle]'); element.addEventListener('pricing-toggle:change', (e) => { console.log(e.detail); }); const pricing = PricingToggle.mount(element); // the block auto() mounted, or a new one pricing.set('yearly'); // rolls to the yearly side pricing.set('monthly', true); // at once, nothing rolled pricing.toggle(); element.addEventListener('pricing-toggle:change', (event) => { console.log(event.detail.period); // 'monthly' or 'yearly' }); // Plans added after the page loaded: destroy, then mount again. PricingToggle.auto(container); // When the table leaves the page: pricing.destroy(); // the markup goes back exactly as it was written
-
Fit it to your page
Two files and the markup, no build: the stylesheet linked in the head, the script loaded before the end of the body.
The engine mounts every
[data-pricing-toggle]on DOM ready. To make the table yours, change the markup, nothing else:- The plans. A plan is any box inside the block. Add one, remove one: the row of the preview shares its width between as many as there are. The engine counts nothing, it only reads the prices it finds.
- The prices. Write both values the way they should read:
data-pricing-toggle-monthly="1 290,00 €"anddata-pricing-toggle-yearly="12 900,00 €",£49and£41,FreeandFree. The currency, the spaces, the commas and the points are yours; the engine never formats a number. Put the monthly value in the text of the element too: it is what a page without JavaScript shows. - The words. Anything else that changes with the period takes
data-pricing-toggle-yearlywith its yearly text: "per month" and "per year", "Billed monthly" and "Billed once a year". - The links. Give the button of a plan its yearly address in
data-pricing-toggle-yearly-href; itshrefis swapped the moment the side changes, before the prices have finished turning. - The switch. A
buttonwithrole="switch", a knob inside, and the word of each side, inside the switch as in the preview or beside it. A click on a word chooses that side. Its size, colours and shape are your classes: the knob travels whatever room the track leaves it.
The colours
The accent is the
colorsetting:data-pricing-toggle-color="#2f6bff"on the block. The two others are custom properties, set in a rule of the page:css .my-pricing { --pricing-toggle-ink: #0b0b0c; /* focus ring, text of a hovered button */ --pricing-toggle-ground: #ffffff; /* hover of the button already in the accent */ }Keeping the choice
data-pricing-toggle-remember="true"keeps the chosen period in local storage under the keypricing-toggleand shows it at once on the next visit, with nothing rolled. Every table that remembers shares that one choice.From JavaScript
`js const pricing = PricingToggle.mount(document.querySelector('[data-pricing-toggle]'));pricing.set('yearly'); // rolls to the yearly side pricing.set('monthly', true); // at once, nothing rolled pricing.toggle(); pricing.period(); // 'monthly' or 'yearly'
document.addEventListener('pricing-toggle:change', (event) => { console.log(event.detail.period); // the side just asked for });
pricing.destroy(); // the markup goes back exactly as it was written
`PricingToggle.auto(container)mounts what was added after the page loaded, withwindow.PricingToggleSettingsas the page-wide settings. -
Avoid the pitfalls
- A face without tabular figures. The stylesheet asks for
font-variant-numeric: tabular-nums; a typeface that has none keeps its proportional digits, and a price then breathes by a pixel or two when a 1 follows an 8. Most text faces have them, Geist does. - A price does not wrap. Its columns stay on one line. A long price in a narrow column needs a smaller size, which is why the preview keeps the word of the period under the price and not beside it.
- The two values are set units under units.
$129and$1,290share their last three columns, and the comma and the leading 1 open at the left. Values that do not line up from the right,$9.99and$99, still work: a point facing a digit turns as one column, and reads a little less like a counter. - Two sources for the monthly price. With
data-pricing-toggle-monthlyon a price, the attribute is what the engine shows and the text is what a page without JavaScript shows. Change one, change the other, or leave the attribute out and keep the text alone. - Values changed after mount are not read again.
destroy(), change the attributes,mount()again. - Keep the noscript line. It hides the switch where no script runs, so nobody presses a switch that cannot answer. The yearly note stays hidden there too, and the page shows the monthly prices.
- A period shown on arrival that is not the monthly one, by
startor byremember, is set when the script runs: put the script before the end of the body, not behind a slow loader, or the monthly side shows for a moment. - The switch of the preview sticks to the top of the window, so a phone keeps it in reach beside the last plan.
position: stickystops working inside an ancestor that clips (overflow: hiddenorauto). The component does not need it; it is the look. - In Webflow the switch is a Link block. Webflow has no button element: the paste writes the switch as a link to
#withrole="switch", and its Embed keeps a click in place and lets Space press it. - **Settings handed to
mount()after the page loaded are dropped.**auto()mounts on DOM ready andmount()on a block already mounted returns the existing instance. A page-wide block goes inwindow.PricingToggleSettings.
- A face without tabular figures. The stylesheet asks for
One click on Copy the AI prompt copies everything an assistant needs to build this effect: the complete code, the markup, the dependency, every setting and every pitfall.
What it does
Pricing toggle is a site utility component for Webflow and vanilla JavaScript, with no library. Also called monthly yearly pricing toggle, monthly annual pricing toggle or pricing table toggle.
A pricing table of two, three or more plans with a monthly or yearly switch, and the switch is a crank. Its knob can be dragged: the prices are geared to it, so they turn while the hand moves, stop when it stops, and fall back if it lets go before the middle. A click, Space, Enter or the arrow keys send the knob to a side and the prices cross after it, unhurried, leaving slowly and settling slowly, so there is time to watch the figures turn. Each price is cut in columns, units under units: a column whose digit changes is a wheel that takes the short way round, a mark both values share never moves, and a column only one value has opens or closes, so a price that gains a digit glides wider instead of jumping. The columns leave one after the other in the order of the page, a wave that crosses the table. What depends on the period follows in the same wave: the word "per month", a line under the price, a note that is there on the yearly side only, the link of each plan's button. Both prices are written as attributes, currency and separators included. The switch is a real one, role switch, named by its yearly word; a screen reader hears the new prices once, in a sentence, never the digits going by. Without JavaScript the page shows the monthly prices. Under reduced motion the values swap with no roll. No library. Pattern type pricing toggle, billing switch, price counter.
Updated
Questions
Add the custom attribute data-pricing-toggle to the section, data-pricing-toggle-switch to the switch, and on each price data-pricing-toggle-price with data-pricing-toggle-monthly and data-pricing-toggle-yearly holding the two prices. The paste already carries all of them; on the canvas the section shows its monthly side and every text stays editable.
Write both values exactly as they should read, data-pricing-toggle-monthly="1 290,00 €" and data-pricing-toggle-yearly="12 900,00 €". The engine never formats a number: it sets the two values units under units, turns the digits that differ and leaves the marks they share in place.
Yes. The switch carries role="switch" and aria-checked, takes Space, Enter and the arrow keys, and is named by the word of its yearly side. After a change the new prices are said once as a sentence, each plan then its price, and the rolling digits are hidden from the screen reader; data-pricing-toggle-announce="false" turns the sentence off.
Yes. A link takes data-pricing-toggle-yearly-href with its yearly address and keeps its href for the monthly one. Any text takes data-pricing-toggle-yearly with its yearly version, "per year" for "per month", and data-pricing-toggle-show="yearly" marks what is there on the yearly side only, a "two months free" note for one.
It needs no library: two springs and one requestAnimationFrame clock that only runs while something moves. data-pricing-toggle-remember="true" keeps the chosen period in local storage and shows it at once on the next visit, and data-pricing-toggle-start="yearly" opens the table on the yearly side.