Accessibility Toolbar Widget: HTML, CSS & JS Snippet

Free accessibility widget in plain HTML, CSS & JavaScript · 10 options · Remembers choices · Try it on the demo page

CategoryMisc

What's included

Features

Floating button in the bottom corner that takes no space in the page layout
Bigger text in three steps, line height and text alignment options
Readable font option using a plain, widely installed typeface
High-contrast and dark inverted modes, with images kept natural when inverted
Grayscale, hide images and pause animations (CSS, video and GSAP)
Highlight links and a reading mask that follows the pointer, touch or keyboard focus
Filters applied to the page blocks, never to the widget, so the button stays fixed and readable
Choices saved in localStorage, restored on load, with a reset button and a safe fallback
Keyboard friendly: aria-expanded, aria-pressed, Esc to close and focus returned to the button
Plain HTML, CSS and JavaScript, exportable to React, Vue and Tailwind from this page

About this UI Snippet

Accessibility Toolbar Widget: A Floating Panel That Lets Visitors Adjust Your Page

Screenshot of the Accessibility Toolbar Widget snippet rendered live

This snippet adds a floating accessibility button to a page, like the Ally and UserWay widgets, without a subscription or a third-party script. Click the round button in the corner of the preview, switch an option on, and watch the demo page change in front of you: the text grows, the colours drain, the images disappear. It is plain HTML, CSS and JavaScript, built from the free Accessibility Toolbar Widget section template, where you can edit the labels and colours visually and export the same code.

Ten options visitors can switch on

The panel groups its options into Text, Visual and Orientation. Bigger text scales the text on the page in three steps, line height adds space between lines, text align cycles left, centre and right, and readable font swaps in a plain, widely installed typeface. Contrast has a high-contrast level and a dark inverted level, grayscale removes colour, hide images clears pictures and background images, and pause animations stops CSS animations, video and GSAP timelines. Highlight links puts every link on a bright underlined background, and the reading mask darkens the page except for a strip that follows the pointer, touch or keyboard focus.

How the options are applied

Each option sets a data attribute on the <html> element, and one injected <style> element holds the rules, such as html[data-a11y-font] body *:not(.a11y *). The :not(.a11y *) part keeps the widget itself out of every rule, so the panel never becomes unreadable. Text size is the one exception: the script records each text element's computed size once and writes a scaled font-size, so layouts using rem, px or clamp() all grow together.

Filters without breaking the button

Putting filter: grayscale(1) on <html> or <body> would also filter the widget, and on <body> it would turn position: fixed descendants into absolutely positioned ones. The script instead adds a small class to the page's other top-level blocks and applies the filter to those, walking down the branch that contains the widget. The fixed button and panel stay sharp and stay where they are.

It remembers each visitor

Choices are written to localStorage inside a try/catch, applied again after the page loads and cleared by the reset button. If storage is blocked, the widget still works for the current page. You can turn memory off with the data-remember="off" attribute.

Keyboard and screen reader basics

The button has an accessible name and aria-expanded, every option is a real button with aria-pressed, Esc closes the panel and focus returns to the button. A widget is a helpful extra, not a substitute for an accessible page: headings in order, alt text, labelled forms, enough contrast and keyboard access still have to be built into the page itself.

Customise and reuse

Rename any label to translate the panel, remove options you do not need by deleting their tiles and their entries in the LEVELS object, and change the brand colour through the --st-brand variable. To use it in a framework, export it as React or Vue, or open the template in the Website Builder.

Step by step

How to Use

  1. 1
    Click the round button in the cornerThe floating accessibility button opens a panel with three groups: Text, Visual and Orientation.
  2. 2
    Switch on Bigger textEach click steps through three sizes and back to normal. The level dots on the tile show where you are.
  3. 3
    Try contrast, grayscale and Hide imagesThe demo page changes at once while the button and the panel stay untouched.
  4. 4
    Turn on Highlight links and the Reading maskLinks get a bright underline, and the page dims everywhere except a strip that follows your pointer.
  5. 5
    Pause animationsThe moving banner and the pulsing Sign up badge stop, which covers both CSS and GSAP animation.
  6. 6
    Reload the preview or press ResetYour choices are remembered on this device; the reset icon in the panel header clears all of them.
  7. 7
    Make it yoursEdit the labels in the HTML, delete options you do not need, or open the Website Section Template to change them visually.

Real-world uses

Common Use Cases

School, council and charity websites
Give pupils, parents and residents a visible way to enlarge text and raise contrast on information-heavy pages, on top of properly built content.
Clinics, shops and service sites
Let older or low-vision visitors make opening hours, prices and booking forms easier to read, or hide busy images and pause moving banners.
Learning page-wide display options
Study how root attributes, an injected style element, filters on sibling blocks and a pointer-following mask combine into one widget.
Related: Skip to Content Link
See the Skip to Content Link for a keyboard shortcut that belongs on every accessible page, next to the toolbar.
Related: Reading Mode Toggle
Compare with the Reading Mode Toggle, which changes one reading setting where this toolbar combines ten.

Got questions?

Frequently Asked Questions

No. A widget helps visitors change how a page looks, but compliance depends on the page itself: heading order, alt text, labelled forms, colour contrast and keyboard access. Use the toolbar as a helpful extra and test your pages.

The script records each text element's computed font size once, then writes a scaled font-size (115%, 130% or 150%) with !important. Going back to level zero restores the original inline values.

A filter on the html or body element would also filter the widget, and on body it makes fixed descendants behave like absolute ones. The snippet applies filters to the page's other top-level blocks and walks down the branch containing the widget.

Yes. They are stored in localStorage and applied again after the page loads, so they carry to other pages on the same site. Reset clears them, and data-remember="off" turns saving off.

Yes. Every label is plain text in the HTML, so edit it to translate. To remove an option, delete its tile and its key in the LEVELS object in the script.

Yes. Click "JSX" for a React component, "Vue" for a single-file component or "Angular" for a standalone component. Initialise the script once after mount, and remove the injected style element when the component unmounts.

Extend with AI

Take This Snippet Further With AI

Paste this snippet's HTML, CSS and JavaScript into an AI coding assistant like Claude and ask it to explain how the filters avoid affecting the fixed widget, or to add a dyslexia-friendly font, a cursor size option, or a keyboard shortcut that opens the panel. It can also review the options against WCAG 2.2 success criteria.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a floating accessibility toolbar in plain HTML, CSS and vanilla JavaScript, with a demo page behind it.

Requirements:
- A fixed round button in the bottom-right corner with an accessible name and aria-expanded. It opens a non-modal panel (role="dialog"); Esc and a close button close it and return focus to the button.
- The panel has a header with a title, a reset icon and a close icon, then three groups of tiles: Text (bigger text with 3 steps, line height with 2 steps, text align left/centre/right, readable font), Visual (contrast with high-contrast then inverted dark levels, grayscale, hide images, pause animations) and Orientation (highlight links, reading mask).
- Each tile is a real button with aria-pressed; clicking steps through its levels and back to off, shown by small level dots.
- Apply options with data attributes on the html element and one injected style element. Exclude the widget from every rule with :not(.a11y *). Scale text by recording each text element's computed font-size once and writing a scaled font-size.
- Apply filters (contrast, grayscale) to the page's other top-level blocks, never to html or body, by adding a class while walking down the branch that contains the widget.
- Pause animations sets animation and transition to none, pauses video and calls gsap.globalTimeline.pause() when GSAP is present.
- The reading mask is two dark fixed bars with a clear strip that follows the mouse, touch and focused element.
- Save choices in localStorage inside try/catch, restore them after the page loads, and clear them with reset. Respect prefers-reduced-motion and hide the widget when printing.
- Include a realistic demo page (header, ticker animation, article with an image, cards, links and a form) so every option visibly changes something.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

Requires
<div class="atw-page">
  <header class="atw-top">
    <a class="atw-brand" href="#"><span class="atw-logo" aria-hidden="true"></span>Maple Grove School</a>
    <nav class="atw-nav" aria-label="Demo menu"><a href="#">News</a><a href="#">Events</a><a href="#">Parents</a><a href="#">Contact</a></nav>
  </header>
  <div class="atw-ticker" aria-hidden="true"><span>Spring concert on Friday &middot; Library open until 6 pm &middot; Charity run sign-ups close Monday &middot; </span></div>
  <main class="atw-main">
    <p class="atw-kicker">Newsletter &middot; Term 3</p>
    <h1>Welcome back to a busy spring term</h1>
    <p class="atw-lead">New clubs, a spring concert and a charity run: here is everything happening at Maple Grove over the next six weeks.</p>
    <img class="atw-photo" src="https://webdevpuneet.com/images/section-templates/hero-landscape.jpg" alt="Green hills at sunrise behind the school fields" width="1200" height="520">
    <h2>Clubs and activities</h2>
    <p>Sign-ups for after-school clubs are now open. Choose from coding, chess, football, choir and art. Places are limited, so please <a href="#">read the full schedule</a> and register by Friday. Every club is free for pupils in years 3 to 6.</p>
    <p>Our reading corner has also been refreshed with over 400 new books. Parents are welcome to borrow them too, and our librarian is happy to <a href="#">suggest titles for every age</a>.</p>
    <div class="atw-cards">
      <article class="atw-card"><h3>Spring concert</h3><p>Friday at 6 pm in the main hall. Doors open at 5:30 pm and entry is free.</p></article>
      <article class="atw-card"><h3>Charity run</h3><p>A 3 km family fun run on Saturday morning, raising money for the new playground.</p><span class="atw-pulse">Sign up</span></article>
      <article class="atw-card"><h3>Parents evening</h3><p>Book a ten-minute slot with your child's teacher using the <a href="#">online booking page</a>.</p></article>
    </div>
    <h2>Contact the office</h2>
    <p>Questions about term dates, meals or transport? Send us a message and we will reply within one working day.</p>
    <form class="atw-form" onsubmit="return false">
      <label for="atw-email">Your email</label>
      <input id="atw-email" type="email" placeholder="you@example.com">
      <button type="submit">Send message</button>
    </form>
  </main>
  <footer class="atw-foot">&copy; 2026 Maple Grove School &middot; An example page for the accessibility toolbar</footer>
</div>
<section class="st st-accessibility-toolbar">
<div class="a11y" data-a11y data-pos="right" data-remember="on">
  <button type="button" class="a11y-fab" aria-expanded="false" data-a11y-open aria-label="Open accessibility tools">
    <svg viewBox="0 0 24 24" width="30" height="30" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="7.500" r="1.200" fill="currentColor"/><path d="M7 10.500l5 1 5-1M12 11.500v3.500M9.500 19l2.500-4 2.500 4"/></svg>
  </button>
  <div class="a11y-panel" role="dialog" tabindex="-1" hidden aria-label="Accessibility">
    <div class="a11y-head">
      <h2 class="a11y-title">Accessibility</h2>
      <button type="button" class="a11y-icon" data-a11y-reset aria-label="Reset all settings" title="Reset all settings"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 3-6.700L3 8"/><path d="M3 3v5h5"/></svg></button>
      <button type="button" class="a11y-icon" data-a11y-close aria-label="Close accessibility tools" title="Close accessibility tools"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.400" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button>
    </div>
    <div class="a11y-body">
      <div class="a11y-sec">
        <h3 class="a11y-h">Text</h3>
        <div class="a11y-grid">
          <button type="button" class="a11y-tile" data-feature="text" aria-pressed="false"><svg class="a11y-ic" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M4 7V5h10v2M9 5v12M6 17h6"/><path d="M15 12h6M18 12v6"/></svg><span class="a11y-lab">Bigger text</span><span class="a11y-pips" aria-hidden="true"></span></button>
          <button type="button" class="a11y-tile" data-feature="lineheight" aria-pressed="false"><svg class="a11y-ic" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M8 6h13M8 12h13M8 18h13"/><path d="M3 8l2-2 2 2M3 16l2 2 2-2"/></svg><span class="a11y-lab">Line height</span><span class="a11y-pips" aria-hidden="true"></span></button>
          <button type="button" class="a11y-tile" data-feature="align" aria-pressed="false"><svg class="a11y-ic" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M4 6h16M4 12h10M4 18h14"/></svg><span class="a11y-lab">Text align</span><span class="a11y-pips" aria-hidden="true"></span></button>
          <button type="button" class="a11y-tile" data-feature="font" aria-pressed="false"><svg class="a11y-ic" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M4 19 10 5l6 14M6.5 14h7"/><path d="M17 11h4M19 11v8"/></svg><span class="a11y-lab">Readable font</span><span class="a11y-pips" aria-hidden="true"></span></button>
        </div>
      </div>
      <div class="a11y-sec">
        <h3 class="a11y-h">Visual</h3>
        <div class="a11y-grid">
          <button type="button" class="a11y-tile" data-feature="contrast" aria-pressed="false"><svg class="a11y-ic" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M12 3v18" /><path d="M12 3a9 9 0 0 1 0 18z" fill="currentColor"/></svg><span class="a11y-lab">Contrast</span><span class="a11y-pips" aria-hidden="true"></span></button>
          <button type="button" class="a11y-tile" data-feature="gray" aria-pressed="false"><svg class="a11y-ic" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 3s6 6.2 6 10.5A6 6 0 0 1 6 13.500C6 9.200 12 3 12 3z"/><path d="M12 3v16.500" /></svg><span class="a11y-lab">Grayscale</span><span class="a11y-pips" aria-hidden="true"></span></button>
          <button type="button" class="a11y-tile" data-feature="images" aria-pressed="false"><svg class="a11y-ic" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="10" r="1.500"/><path d="m21 16-5-5-9 8M3 3l18 18"/></svg><span class="a11y-lab">Hide images</span><span class="a11y-pips" aria-hidden="true"></span></button>
          <button type="button" class="a11y-tile" data-feature="motion" aria-pressed="false"><svg class="a11y-ic" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M10 9v6M14 9v6"/></svg><span class="a11y-lab">Pause animations</span><span class="a11y-pips" aria-hidden="true"></span></button>
        </div>
      </div>
      <div class="a11y-sec">
        <h3 class="a11y-h">Orientation</h3>
        <div class="a11y-grid">
          <button type="button" class="a11y-tile" data-feature="links" aria-pressed="false"><svg class="a11y-ic" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10 14a4.500 4.500 0 0 0 6.400 0l3-3a4.500 4.500 0 0 0-6.400-6.400l-1 1"/><path d="M14 10a4.500 4.500 0 0 0-6.400 0l-3 3a4.500 4.500 0 0 0 6.400 6.400l1-1"/></svg><span class="a11y-lab">Highlight links</span><span class="a11y-pips" aria-hidden="true"></span></button>
          <button type="button" class="a11y-tile" data-feature="mask" aria-pressed="false"><svg class="a11y-ic" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M3 15h18"/></svg><span class="a11y-lab">Reading mask</span><span class="a11y-pips" aria-hidden="true"></span></button>
        </div>
      </div>
      <p class="a11y-note">Your choices are saved on this device only.</p>
    </div>
  </div>
</div>
</section>