שייך להגדרת הווידג'ט

שייך / תיעוד

התיעוד הטכני

התיעוד המלא למפתחים ולסוכני AI. הוא כתוב באנגלית.

הטקסט הגולמי (Markdown)

Shayach: technical reference for AI agents and developers

This is the complete technical reference for Shayach: a free, open-source accessibility widget, hosted at shayach.co.il, plus a generated accessibility statement (הצהרת נגישות) for Israeli websites. One script tag adds a customizable accessibility button and panel (Hebrew, Arabic, Russian, English) and can publish a statement page generated from the site's configuration. A weekly agent pipeline tracks Israeli accessibility law (Regulation 35, IS 5568) and ships updates through a versioned URL, so every site stays current without touching its code.

Other documents:

  • README.md: the Hebrew overview for site owners and decision makers.
  • docs/guide.md: a Hebrew step-by-step guide for site owners with no technical background (per-platform install instructions, the statement, troubleshooting). Point site owners there.
  • llms.txt: a short summary of this document for AI agents.
  • docs/maintainers.md: compliance automation and releasing, for maintainers of this repository.

Everything here matches the code. If this document and the JSON Schema at https://shayach.co.il/v1/config.schema.json ever disagree, the schema wins.

Contents

No immunity. Read this first.

This widget does not protect a site from accessibility lawsuits. Not fully, not partially, not at all. No accessibility widget does. If anyone says their widget makes a site "fully compliant", "lawsuit-proof" or comes with "legal protection", that is marketing, not law. Don't trust it.

Israeli law (Regulation 35) looks at the website itself: does it meet IS 5568 at level AA, and does it publish an accurate accessibility statement. A widget cannot fix inaccessible markup, missing alt text, poor contrast or forms that don't work with a keyboard.

The Commission for Equal Rights of Persons with Disabilities says the same in its official FAQ (gov.il, "האם אני חייב להתקין באתר שלי 'סרגל נגישות'?"):

יש להדגיש כי שילובם של סרגלים אלו אינו פוטר מחובת העמידה בכל דרישות התקנות והתקן האמורים לעיל ביחס לאתר אינטרנט, או מביצוע כל התאמות הנגישות הנדרשות. עוד יודגש, כי אין כל חובה להשתמש ב"סרגל נגישות" על מנת להגיע לרמת הנגשה הנדרשת בחוק ובתקנות.

In short: adding a toolbar does not exempt a site from any requirement, and the law does not require a toolbar at all.

What this project actually does is help:

  • It gives visitors adjustments that make the site easier for them to use.
  • It generates an accessibility statement that follows the official template and updates its wording when the requirements change, to reduce the risk that a missing or outdated statement is the reason the site gets a complaint.
  • It gives people a clear way to report accessibility problems to the site owner, so the owner hears about them and can fix them before they turn into claims.

The site owner is still responsible for the site's accessibility: build it accessibly (see What it does not do) and have it reviewed by an accessibility professional. When you install the widget for an owner, tell them this in plain words.

This project is not legal advice.

Agent procedure

Follow these steps when installing or updating the widget in a project.

  1. Read this document in full and the config JSON Schema (https://shayach.co.il/v1/config.schema.json).
  2. Inspect the site's stack. Find the one place that renders on every page: the root layout or document (app/layout.tsx, pages/_document.tsx, index.html, nuxt.config.ts, an Astro base layout, a CMS theme footer). See Frameworks and platforms. Check whether the site sends a Content Security Policy (response headers or a CSP <meta> tag).
  3. Ask the owner only for facts only they know. These are the statement details: the organization's name, the people to contact about accessibility (name, phone, email), whether there is a physical place that serves the public and how it is accessible, which accessibility work was actually done on the site, and whether the site was ever audited (and at which level). Take brand colors, the site language and URLs from the project itself. Use today's date for updatedAt only when the owner reviews the statement with you today.
  4. Generate the config and the tag. Build window.A11yConfig (see Configuration) and add the config script and the widget script once, so they load on every page (see Install).
  5. Add the statement page and the footer link. Create a statement page with the container and statement.js (see Publish the statement), set statementUrl to it on every page, and add a footer link named "הצהרת נגישות" (or the same name in the page language) on every page. Both are required: at least two ways to reach the statement.
  6. Verify in a browser (a real one or a headless one):
    • the round accessibility button is visible in the expected corner after the page loads;
    • clicking it opens the panel;
    • an adjustment changes the page (for example, open the "Text adjustments" card, increase font size and check that text grows, or turn on dark contrast in the "Color adjustments" card);
    • the console shows no errors, and no [shayach] warnings you did not expect (each warning names the config field it ignored);
    • the statement page renders the statement, with no "missing details" list at the top unless the owner has not provided those details yet;
    • if the site has a CSP, nothing is blocked (see Content Security Policy).
  7. Tell the owner what you installed, what details are still missing from the statement, and that the widget gives no legal protection and does not replace fixing the site itself. Point them to docs/guide.md for the Hebrew explanation.
  8. Updates: to check for updates later, read the changelog (CHANGELOG.md or https://shayach.co.il/v1/changelog.json) from the version the project last saw. Apply every [Action required] entry; nothing else needs work, because every 1.x release reaches the site automatically.

Do not

  • Never claim compliance. Never tell an owner, write in a commit, a page or a statement that the widget makes the site compliant, legal, accessible, "fully accessible" or protected from lawsuits.
  • Never invent statement facts. No made-up names, phone numbers, emails, dates, adjustments, measures, tested browsers or assistive technologies. Missing required details are listed at the top of the statement until the owner provides them; that is the intended behavior.
  • Never set conformanceLevel without an audit. Set it only when the site was actually audited at that level, and only with the owner's confirmation.
  • Never decide that an owner is exempt. Report the exemption facts and send the owner to the Commission or a lawyer.
  • Never hide the no-immunity notice from the owner. When you summarize the install, say that the widget does not give legal protection and does not replace making the site accessible.
  • Don't load widget.js more than once per page, don't self-host a copy (you would stop receiving updates), and don't pin a full version: use the /v1/ URL.
  • Don't install it next to another accessibility widget or plugin; remove the other one first.
  • Don't style or remove the widget's own elements (#shayach, #shayach-overlay).

Install

Add the config (optional) and the script once, before </body>, on every page (in a framework: the root layout):

<script>
  window.A11yConfig = {
    icon: { shape: "circle" },
    theme: { primary: "#1f5eff" },
    // optional: the URL of your accessibility statement page, if you have one
    statementUrl: "/accessibility-statement"
  };
</script>
<script src="https://shayach.co.il/v1/widget.js" defer></script>

To also publish the accessibility statement page, see Accessibility statement.

Nothing else is needed. The widget waits until the page has loaded, then adds one button in its own Shadow DOM. Until a visitor uses it, it adds nothing to the page's <head>, makes no other requests, and writes no storage.

Details:

  • Placement: before </body> is recommended. With defer it also works in <head>.
  • defer: keep it. The script never blocks rendering.
  • Config timing: widget.js reads window.A11yConfig once, at DOMContentLoaded (or immediately if the script runs after that). Any inline config script in the page's HTML is therefore picked up, wherever it is. A config set later, for example from a framework component after hydration, is not picked up. The simple rule: keep the config in a plain inline script placed before the widget script.
  • When the button appears: after the page's load event and the next idle moment, or after 3 seconds, whichever comes first.
  • Returning visitors: if the visitor has saved adjustments, the widget loads the panel code (app.js) right away to re-apply them.
  • Loaded twice by mistake: a second copy does nothing.
  • Single-page apps: load it once in the root layout, not per route. The button survives client-side navigation, including routers that replace the whole <body> (Turbo Drive, some SPA routers). Adjustments also apply to content added later. The statement page is different: statement.js renders only the containers present when it first runs (see Publish the statement).
  • What it adds to the DOM: <div id="shayach"> (the button and panel, in an open shadow root) at the end of <body>, and <div id="shayach-overlay"> only while a reading guide or reading mask is on. One global: window.A11yConfig, which the site defines.
  • Where it loads from: app.js, font files and statement chunks load from the same /v1/ directory as widget.js, only when needed.

Frameworks and platforms

Next.js (App Router). In app/layout.tsx, inside <body>, with next/script:

import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="he" dir="rtl">
      <body>
        {children}
        <Script id="shayach-config" strategy="beforeInteractive">
          {`window.A11yConfig = { statementUrl: "/accessibility-statement" };`}
        </Script>
        <Script src="https://shayach.co.il/v1/widget.js" strategy="afterInteractive" />
      </body>
    </html>
  );
}

The config uses beforeInteractive so it exists before the widget runs; the widget uses afterInteractive (or lazyOnload). Don't use strategy="worker": the widget needs the main thread and the DOM.

Next.js (Pages Router). Put the same two <Script> elements in pages/_document.tsx (beforeInteractive is only allowed there) or put the config in _document.tsx and the widget <Script> in pages/_app.tsx.

React (Vite, Create React App) and other client-rendered apps. Paste the plain HTML snippet before </body> in index.html (Vite: the project root; Create React App: public/index.html). Don't mount the script from a component.

Vue (Vite). Paste the plain HTML snippet into index.html.

Nuxt 3 and 4. In nuxt.config.ts:

export default defineNuxtConfig({
  app: {
    head: {
      script: [
        { innerHTML: 'window.A11yConfig = { statementUrl: "/accessibility-statement" };', tagPosition: "bodyClose" },
        { src: "https://shayach.co.il/v1/widget.js", defer: true, tagPosition: "bodyClose" },
      ],
    },
  },
});

Astro. In the base layout, before </body>, mark both scripts is:inline so Astro does not bundle them:

<script is:inline>
  window.A11yConfig = { statementUrl: "/accessibility-statement" };
</script>
<script is:inline src="https://shayach.co.il/v1/widget.js" defer></script>

SvelteKit: src/app.html before </body>. Angular: src/index.html before </body>. Plain HTML: every page, before </body> (or a shared footer include).

WordPress, Wix, Shopify, Webflow, Squarespace: use the platform's site-wide footer code setting. Step-by-step instructions (in Hebrew) for each are in docs/guide.md. In short: WordPress with a header and footer code plugin (for example WPCode); Wix: Settings, Custom Code, Body end (premium plan with a connected domain); Shopify: theme.liquid before </body>; Webflow: Site settings, Custom code, Footer code (paid site plan); Squarespace: Code Injection, Footer (plan with code injection). Caveats: Shopify theme code does not reach the checkout pages; WordPress.com runs scripts only on a plan that allows plugins; with Elementor, use its HTML widget for the statement container. Remove any other accessibility widget or plugin first: two accessibility buttons confuse visitors. After installing on WordPress, clear the caching plugin's cache (for example WP Rocket or LiteSpeed Cache).

Google Sites does not allow site-wide scripts, so the widget cannot be added there.

Configuration

All options are optional. The full machine-readable definition is the JSON Schema at https://shayach.co.il/v1/config.schema.json. Invalid values never break the page: they are ignored and reported in the browser console with the prefix [shayach], as the option's path, the value and what the widget used instead (for example [shayach] icon.size: 120 is not valid; using 96.). The allowed values are in the tables below and in the schema.

Top-level keys of window.A11yConfig: language, icon, theme, statementUrl, statement. Unknown keys at any level are ignored with a warning.

OptionTypeDefaultNotes
language"auto", "he", "ar", "ru", "en""auto"auto uses the page's lang attribute (a region such as he-IL is fine; iw counts as Hebrew), otherwise Hebrew. A visitor's own choice in the panel always wins.
statementUrlURLnoneLink to the statement page. The panel footer's "הצהרת נגישות" links to it (new tab). The about card's button opens the statement window when statement is set, and the window then also links to this page (new tab); without statement the about card's button is a plain link to this page, because the widget has no statement data to show.
statementobjectnoneDetails for the generated accessibility statement. See Accessibility statement. null counts as not set. When set, the panel's about card opens it in the statement window.
icon.shape"circle", "rounded", "pill", "edge-tab""circle"pill and edge-tab show a text label.
icon.position"auto", "top-right", "top-left", "upper-right", "upper-left", "middle-right", "middle-left", "lower-right", "lower-left", "bottom-right", "bottom-left""auto"auto is bottom-right on RTL pages, bottom-left on LTR pages. upper and lower sit a quarter and three quarters of the way down (top: calc(offsetY + (100% - 2 * offsetY - size) * 0.25), or 0.75). The four upper/lower values need widget 1.6.0: an older cached loader warns and uses the default.
icon.offsetX, icon.offsetYnumber, 0 to 20020Distance from the edges in px. For the middle positions, offsetY shifts the button down from the vertical center; for upper and lower it is the margin kept at the top and bottom of the screen. edge-tab ignores offsetX.
icon.sizenumber, 44 to 9656Button height in px.
icon.background, icon.foregroundCSS colortheme.primary, best of black/whiteA foreground below 4.5:1 contrast is replaced automatically.
icon.labelTextstring, up to 40 charactersthe word for accessibility in the panel languageLabel for pill and edge-tab. Longer text is cut to 40 characters.
icon.labelLanguage"he", "ar", "ru", "en"follows the panel languageFixes the built-in label's language.
icon.mobile{ position, offsetX, offsetY, size }same as desktopApplies on screens up to 640px wide. position here has no auto.
icon.draggablebooleantrueVisitors can drag the button (it snaps to the nearest of the ten positions) or choose a position in the panel's "Button position" card, which also works with a keyboard. The choice is stored in the visitor's browser (shayach:v1, per origin: www and the bare domain are separate), wins over icon.position and icon.mobile.position on every screen size (your offsets and size still apply), and Reset settings removes it. false keeps the button where you put it, hides the card, and ignores (but keeps) a stored choice.
theme.primaryCSS color#1f5effHeader, selected tiles, tile icons.
theme.onPrimaryCSS colorbest of black/whiteText on primary.
theme.background, theme.text, theme.muted, theme.border, theme.surfaceCSS color#ffffff, #1d1d1f, #5f6672, #d5dbe7, #f3f6ffPanel colors. Text colors below 4.5:1 contrast are replaced automatically, so the panel always meets WCAG 2.2 AA.
theme.scrollbarCSS colortheme.primaryScrollbar thumb inside the panel and the statement window (the track is theme.surface). Below 3:1 against theme.surface (WCAG 1.4.11) it is replaced with theme.text, with a warning.

Validation rules:

  • Numbers outside their range are clamped to the nearest allowed value (and rounded), with a warning. A non-number is ignored.
  • URLs (statementUrl) must be http(s) or relative; anything else (for example javascript:) is ignored.
  • Colors must be valid CSS colors (hex, rgb(), hsl(), named colors). An invalid color falls back to the default.

The button always shows the universal accessibility symbol, in one of the four shapes. The built-in label is "נגישות" (Hebrew), "إمكانية الوصول" (Arabic), "Доступность" (Russian) or "Accessibility" (English).

Theme contrast auto-correction and warnings

The widget guarantees WCAG 2.2 AA text contrast inside its own UI, whatever colors the site chooses:

  • theme.text and theme.muted below 4.5:1 on theme.background, theme.onPrimary below 4.5:1 on theme.primary, and icon.foreground below 4.5:1 on the icon background are replaced with black or white (whichever contrasts more), with a warning that states the measured ratio.
  • theme.surface is painted under text when a tile or button is hovered. If theme.text on it is below 4.5:1, the surface is replaced with the default surface (or the panel background).
  • If theme.primary on theme.background is below 3:1, a warning says the tile icons will be hard to see. That color is not replaced.
  • theme.scrollbar (or theme.primary when it is not set) below 3:1 against theme.surface is replaced with theme.text, with a warning.
  • Colors whose contrast cannot be measured (CSS variables, keywords such as currentColor, translucent colors) are replaced with the default, with a warning. Use solid hex, rgb(), hsl() or named colors.

The panel's colors are resolved by app.js, so these warnings appear in the console when the panel first opens, not on page load. The button's own colors are corrected by widget.js before the button appears, without a warning at that point; the icon.foreground warning is logged with the others when the panel first opens.

A dark panel, for example:

window.A11yConfig = {
  theme: {
    primary: "#8ab4ff",
    background: "#16181d",
    text: "#f2f4f8",
    muted: "#b4bccb",
    border: "#3a404c",
    surface: "#232833"
  }
};

A pill-shaped button with a custom label on the middle of the left side, and a smaller button at the bottom on phones:

window.A11yConfig = {
  icon: {
    shape: "pill",
    labelText: "נגישות",
    position: "middle-left",
    mobile: { position: "bottom-left", size: 48 }
  }
};

A button that visitors cannot move, a quarter of the way down the right side, with a different scrollbar color in the panel:

window.A11yConfig = {
  icon: { position: "upper-right", draggable: false },
  theme: { primary: "#B93A22", scrollbar: "#3B2032" }
};

Accessibility statement

Israeli law requires a website to publish an accessibility statement (הצהרת נגישות). This project generates the statement text in Hebrew, Arabic, Russian and English from the statement config. The text follows the Commission's official template and the confirmed requirements in compliance/legal-state.json, and is kept current by the compliance agents. It cites IS 5568, which is based on WCAG 2.0. A generated statement is text only: it does not make a site accessible and gives no legal protection.

Publish the statement

When statement is set, the panel shows the generated statement in a window inside the widget (a modal dialog over the page, with fact boxes, the contact details first, one block per section of the official template, and a print button that prints only the statement). The about card's "להצהרת הנגישות המלאה" opens it; the footer's "הצהרת נגישות" links to statementUrl when set and otherwise opens the same window. The statement describes the widget's panel, so widget.js must be installed on the site: statement.js alone does not add the panel.

The statement window alone is not enough. The Commission's guide asks for at least two ways to reach the statement, and the regulation asks for a prominent place, so publish a statement page and link it from the footer and from the widget (statementUrl). Create a statement page with this container and script, using the same window.A11yConfig:

<script>
  window.A11yConfig = {
    statementUrl: "/accessibility-statement",
    statement: {
      organizationName: "Example Ltd",
      updatedAt: "2026-10-02",
      adjustments: ["All images have text alternatives", "Every page can be used with a keyboard"],
      coordinator: { name: "Dana Levi", phone: "03-1234567", email: "access@example.com" },
      noPhysicalService: true,
      knownLimitations: ["Videos in the archive have no captions"]
    }
  };
</script>
<div data-shayach-statement></div>
<script src="https://shayach.co.il/v1/statement.js" defer></script>

Then set statementUrl to that page on every page, so the widget links to it. The simplest setup keeps one window.A11yConfig (with statement and statementUrl) in the site-wide snippet, and puts only the container and statement.js on the statement page.

At least two ways to reach the statement are required. The Commission's guide says the statement must be reachable in at least two ways, with consistent link names. Use the widget's panel link (statementUrl) plus a link in the footer of every page, both named "הצהרת נגישות" (or the same name in the page language).

Container attributes (all optional):

  • data-lang: he, ar, ru or en. Without it, the statement uses the lang of the nearest ancestor with a lang attribute, then the page's lang, otherwise Hebrew.
  • data-heading-level: a whole number from 1 to 4, the level of the statement's title; section headings are one level lower. Default 2. Any other value falls back to 2. (In the statement window the title is an h2 and sections are h3.)
  • data-layout: blocks renders the block layout the statement window uses (fact boxes for the updated date, the organization and the standard, the contact block first, one .shayach-block per section with a line icon). It comes with a minimal style, scoped to .shayach-statement-blocks, that keeps the page's font and colors and adds boxes, borders and spacing. Tune it with CSS custom properties set on the container or any ancestor: --shayach-accent (icons, the contact block's border, the missing-details marker; default currentColor), --shayach-surface (box background; default transparent), --shayach-line (box borders; default currentColor at 30%) and --shayach-muted (fact labels; default inherited). Without data-layout the plain markup is unchanged. The configurator's statement page code uses data-layout="blocks".

The container attribute's older name, data-il-a11y-statement, is still accepted; use data-shayach-statement in new pages.

Notes:

  • window.A11yConfig must be defined before statement.js runs. Like widget.js, it renders at DOMContentLoaded (or immediately if it runs later), so an inline config anywhere in the page's HTML works. A config defined after the script has run is not picked up.
  • If the container sits inside a framework-managed component that re-renders, the framework may wipe the rendered statement. Put the container in static HTML, or render it after hydration.
  • statement.js renders only the containers present when it first runs (at DOMContentLoaded); it does not watch for containers added later. In client-routed apps (Next.js, React Router, Vue Router, Nuxt), keep the container in the server-rendered HTML of the statement page and make every link to that page a full page load (a plain <a href>, not the router's link component). statementUrl in the panel already opens the page in a new tab.
  • Several containers on one page are all rendered; each is rendered only once, even if statement.js is included twice, and one broken container does not stop the others.
  • The output is a <section class="shayach-statement"> with plain headings, paragraphs and lists and no styles of its own, so it takes the page's typography. With data-layout="blocks" it is a <section class="shayach-statement shayach-statement-blocks">, and statement.js adds the block layout's small scoped style sheet to the page once, as a constructable stylesheet.
  • On page builders whose HTML blocks run in an iframe (for example Wix's embed element), the config with statement must be inside the same embed as the container and statement.js, because the iframe has its own window. Keep the statement details only in that embed (the site-wide config needs only statementUrl, so the panel's footer and about card link to the page), so the two copies cannot drift apart.

Statement fields

All fields are optional in the schema, but the required items below must be provided for a complete statement.

FieldMeaning
organizationNameName of the organization. Required.
organizationDescriptionOne line describing the organization or the site.
commitmentYour commitment to accessibility, in your own words.
updatedAtDate the statement was last reviewed, YYYY-MM-DD (a real calendar date). Required.
conformanceLevel"A", "AA" or "AAA". Set it only for a level the site was actually audited at; otherwise the statement states no level.
additionalStandardsOther standards the site was checked against, for example ["WCAG 2.2 AA"].
adjustmentsAccessibility adjustments the site offers. Required.
reviewFrequencyHow often accessibility is reviewed, for example "every 6 months".
measuresMeasures taken to keep the site accessible, such as audits or staff training.
browsersBrowsers the site was tested with.
assistiveTechAssistive technologies the site was tested with.
knownLimitationsKnown parts of the site that are not accessible, in plain language ("videos have no captions"), not criterion numbers.
thirdPartyPages[{ url, description }]: pages whose content comes from third parties. The statement then declares partial conformance for them. url is required (http(s) or relative).
exemptions[{ scope, type, expires, alternatives }]: parts of the site for which you rely on a legal exemption (scope and type are required; type is financial, technological or other; a technological exemption must also list alternatives, per regulation 35ו(ג)). See Exemptions.
stage{ description, completionDate, alternatives }: use when the site is still being made accessible. description is required.
temporaryNotices[{ text, until }]: current notices about temporary accessibility faults. Remove them when fixed.
physicalArrangementsAccessibility arrangements at the physical premises. Required unless noPhysicalService is true.
noPhysicalServicetrue when there is no physical place that serves the public.
coordinator{ name, phone, email, other }: the accessibility coordinator.
contact{ name, phone, email, other }: a general accessibility contact.

All dates (updatedAt, expires, completionDate, until) are YYYY-MM-DD.

Owner text in several languages. Every text you write (the organization name and description, commitment, reviewFrequency, physicalArrangements, each item of additionalStandards, adjustments, measures, browsers, assistiveTech and knownLimitations, a third-party page's description, an exemption's scope and alternatives, the stage's description and alternatives, a notice's text, and a contact's name and other) is either one string or an object with one value per language (he, ar, ru, en; at least one):

organizationName: { he: "מאפיית הדוגמה", en: "Example Bakery" },
adjustments: [
  { he: "ניווט מלא במקלדת", en: "Full keyboard navigation" },
  "NVDA",
],

The statement shows the value in its own language, else the value in the page's language (<html lang>), else the first value given. A plain string is shown as written in every language, so write it in the site's language. Text that is not in the statement's language is marked with its own language (a plain string counts as the page's language) so screen readers pronounce it correctly. Phone numbers, emails, URLs and dates are never per-language.

Required items and how missing ones are handled

The required items (REQUIRED_ITEMS in src/statement/model.ts) are checked like this:

Required itemSatisfied when
Organization nameorganizationName is a non-empty string.
Updated dateupdatedAt is a valid YYYY-MM-DD date.
Adjustments madeadjustments has at least one non-empty string.
Contacta valid coordinator or contact exists: an object with at least a phone, email or other (a name alone is not enough).
Physical arrangementsphysicalArrangements is set, or noPhysicalService is true.

Required details that are missing are listed at the top of the statement and logged as [shayach] console warnings. Nothing is invented.

Rendering rules

  • The statement title, then the updated date, then the "missing details" notice (if any), then the organization's introduction and commitment.
  • Fixed sections from the official template (what an accessible site is, the legal standard IS 5568 based on WCAG 2.0) are always shown.
  • The adjustments list shows the owner's adjustments plus one fixed item describing the widget's panel. Owner adjustments should therefore describe what was done on the site itself.
  • A conformance level appears only when conformanceLevel is set.
  • Optional sections (temporary notices, measures, known limitations, exemptions, third-party pages, tested browsers and assistive technologies, accessibility stage, physical arrangements) appear only when their fields are set.
  • In the statement window (and with data-layout="blocks") the contact section comes first, right after the fact boxes, with its introduction and the demand sentence; it is not repeated at the end.
  • Contact: if both coordinator and contact are set, only coordinator is shown. A phone number becomes a tel: link (plain text if it has no digits); an email becomes a mailto: link.
  • The statement never says the site is fully accessible.
  • Limits: each text value is cut to 2,000 characters and each list to 50 items. Every value is inserted as text, never as HTML.

Statement warnings

Besides the missing-items warning, these are logged as [shayach] warnings and the value is ignored: an unknown statement key, an invalid date, a conformanceLevel other than A, AA or AAA, an invalid email, a phone number that is not digits with optional + ( ) - and spaces (at least 6 characters), a contact with only a name, a third-party page without a valid URL, an exemption without a scope or with an unknown type, and a technological exemption without alternatives (logged; the exemption is still shown), and in per-language text a key other than he, ar, ru or en or a value that is not a string (that value is ignored, the others are kept).

Exemptions

Some small providers may be exempt from the website accessibility requirements, under certain conditions. The two official sources differ, and this project does not decide which one applies to anyone. It never encodes a turnover threshold in its logic and never decides that anyone is exempt; the statement's exemptions section only repeats what the owner configures.

  • Regulation 35ו(ז), as consolidated on Nevo, exempts an עוסק פטור or a provider whose average annual turnover does not exceed 100,000 ILS.
  • The Commission's guide "נגישות אתרי אינטרנט" (updated 16.03.2026, gov.il) gives the threshold as 120,000 ILS.
  • Because the two official sources differ, a provider that is not an עוסק פטור and has turnover between 100,000 and 120,000 ILS should not assume it is exempt, and should check with the Commission or a lawyer.
  • A provider with average annual turnover up to 1,000,000 ILS may be entitled to a renewable 3-year exemption only for a site first operated before 26 October 2017, conditional on publishing their contact details accessibly. A new site must be accessible.
  • Per the Commission, these turnover exemptions do not apply to public authorities.

This is not legal advice.

Content Security Policy

If the site sends a CSP header, allow https://shayach.co.il in script-src. This also covers statement.js on the statement page, and the statement window, which loads one chunk from the same /v1/ path only when a visitor opens it and adds its styles (and, while printing, a print-only sheet on the page) as constructable stylesheets. If you want the dyslexia font to work, also allow https://shayach.co.il in font-src. That is all: the widget needs no 'unsafe-inline' in style-src, because it adds its styles as constructable stylesheets, which a Content Security Policy does not block. The same goes for the style sheet of the statement page's block layout (data-layout="blocks").

The one exception is a site that sends its CSP as a header (with no nonce on its scripts or styles and no CSP <meta> tag) and either uses CSS cascade layers (for example Tailwind v4) or loads a stylesheet from another origin without CORS (for example the Google Fonts CSS), whose rules the widget cannot read. While an adjustment is active, the widget then tries to add a one-line <style> first in <head> that orders its layer before the site's. The header CSP blocks it, which the browser reports as one violation per page load, only after a visitor turns on an adjustment. A CSP set in a <meta> tag, or any nonce on the site's scripts or styles, is detected and the <style> is skipped, so those sites get no report.

Example additions to an existing policy:

script-src 'self' https://shayach.co.il;
font-src 'self' https://shayach.co.il;

What it does

For visitors, a floating button opens a simple panel (a side drawer on the button's side, checked each time the panel opens; a full-screen sheet on phones) with:

  • Profiles (one click each):

    ProfileTurns on
    Seizure safestop animations, low saturation
    Vision impairedfont size 130%, high contrast, big cursor, highlight links
    ADHD friendlyreading mask, stop animations, highlight headings
    Cognitivereadable font, highlight links, highlight headings, reading guide
    Dyslexiadyslexia font, letter spacing, line height
    Keyboard navigationemphasize focus, highlight links, highlight headings
  • Text: font size (100% to 200% in steps of 10%), line height (1.8), letter spacing (with word spacing), readable font, dyslexia font (OpenDyslexic, downloaded only when turned on), align text (text blocks aligned to the start side), highlight links, highlight headings.

  • Color: dark, light and high contrast, monochrome, low and high saturation.

  • Navigation and display: stop animations, hide images, emphasize focus, big cursor, reading guide, reading mask, and page structure (jump to the page's headings and regions).

  • Read aloud with adjustable reading speed (50% to 200%), using the browser's built-in voices: click or focus any text to hear it. Escape stops reading.

  • Cards: each group (profiles, text, color, navigation and display, read aloud) is a collapsible card, one open at a time, with a badge counting the adjustments that are on in it. The open card is remembered for the visit (sessionStorage key shayach:card).

  • Button position: drag the button to one of ten spots (five on each side), or pick one in the "Button position" card (a 5 x 2 grid of toggle buttons, also by keyboard, with a button back to the default). Saved in the visitor's browser; Reset settings restores the owner's position. Dragging works while the panel is closed: dashed outlines show the ten spots, the button snaps to the nearest one on release, and Escape cancels the drag. With icon.draggable: false there is no dragging and no position card.

  • About the accessibility menu: what the menu is, the tools this build has, a note that it complements but does not replace an accessible site, and the way to the full statement.

  • Language switch, accessibility statement, reset, and hide widget (hides the button for the rest of the visit).

Options that cannot be combined are exclusive: one contrast mode at a time, one of readable font and dyslexia font, one of reading guide and reading mask, one of monochrome and the saturation modes. Settings are remembered as the visitor moves between pages. The panel works with a keyboard and screen readers, and Escape closes it.

For site owners, everything visual is configurable: the button's colors, shape, size, position and label, whether visitors may move it, and the panel's colors and scrollbar color. The button always shows the universal accessibility symbol. The panel's buttons are fixed so every site offers the same complete set. The generated statement is described in Accessibility statement.

What it does not do

Israeli law (Regulation 35 of the 2013 Service Accessibility Regulations) requires the site itself to meet IS 5568 at level AA and to publish an accessibility statement. A widget cannot fix inaccessible markup. This project gives the widget and the statement; the site still needs:

  • text alternatives for images,
  • sufficient color contrast in the design,
  • full keyboard operability and visible focus,
  • labeled form fields and meaningful headings and landmarks,
  • captions for video content.

Known technical limits of the widget:

  • Stop animations stops CSS animations, transitions and smooth scrolling, pauses autoplaying videos (including ones added later) and freezes same-origin GIFs. It cannot stop every JavaScript-driven animation. Cross-origin GIFs without CORS keep playing, and on sites whose CSP blocks data: images, frozen GIFs may appear blank.
  • Hide images keeps images in the layout and available to screen readers. It does not hide <canvas>, so maps and charts keep working.
  • Page structure lists the visible headings and landmarks (forms and regions only when they have a name) from the page's markup, so it only shows what the markup provides.
  • Reading guide and reading mask follow the mouse and are mouse-only; they appear on desktop only. On touch-only devices they are disabled with an explanation.
  • Read aloud reads on click or keyboard focus, using the device's voice for each element's language. It depends on the visitor's device: Hebrew and Arabic voices are good on iOS, Android and Edge, but missing on some desktop browsers. When the page language has no voice, the button is disabled with an explanation. Some browsers stop online voices after a short time, so the widget prefers voices installed on the device.
  • Dyslexia font (OpenDyslexic) covers Latin script; for Hebrew, Arabic and Russian characters it lacks, the browser falls back to Arial automatically.
  • Dark and light contrast replace page colors with solid ones and remove decorative background images, because text over a background photo cannot be made readable otherwise. Images, videos and SVG keep their colors. Icons drawn with CSS masks can disappear under dark or light contrast.
  • Colors and font sizes that a page writes into an element's own style attribute with !important are overridden. Other properties set that way (for example a font family or a background on a link) stay as the page set them.
  • Letter spacing never spaces Arabic letters, but it can only recognize Arabic text by its lang attribute. Arabic text on a page or element without an Arabic lang may get letter spacing.
  • Monochrome, saturation and high contrast apply a filter to the whole page, including the accessibility panel itself.
  • On sites with a strict Content Security Policy (no 'unsafe-inline' in style-src) that also use cascade-layer !important utilities (for example Tailwind v4 text-black!), those utilities can still win over the widget's colors and fonts.
  • Content inside cross-origin iframes, canvas-rendered text, and closed shadow roots cannot be adjusted.
  • Content inside open shadow roots (web components) gets the same style adjustments, font size and line height as the rest of the page, for up to 500 shadow roots per page. Not inside them: contrast's override of inline !important colors, stop animations' pausing of videos and GIFs, and page structure. Browsers without constructable stylesheets get no style adjustments inside shadow roots. A shadow root that a script adds to an element already on the page is found only when that element is a custom element being defined. A component whose own styles use cascade-layer !important rules can still win over the widget's colors and fonts inside it. A component that replaces its own adopted stylesheets after the widget found it loses the widget's styles until the next change of an adjustment.

Standards: WCAG 2.2 AA

This project targets WCAG 2.2 level AA for its own interface, which goes beyond what Israeli law currently requires:

  • The law: the current Israeli standard, IS 5568 part 1 (September 2023), is based on WCAG 2.0 with a few national changes, at level AA. Some sources claim it is based on WCAG 2.1 or 2.2; the official text says 2.0.
  • This project: the widget's own interface meets WCAG 2.2 AA plus IS 5568's national additions (for example, 2.4.10 Section Headings is required at AA). Since WCAG 2.2 builds on 2.0, meeting it also covers the legal baseline for the widget's own interface. It says nothing about the rest of the site.
  • The site: we recommend building to WCAG 2.2 AA too. The accessibility statement states the standard and level the owner configures, never a level they have not claimed.

Privacy, footprint and performance

  • Privacy: no cookies, no analytics, no tracking and no account. The widget sends no visitor data anywhere; the only network requests are for its own files from the /v1/ path. The visitor's adjustments, language and chosen button position are stored only in the visitor's own browser (localStorage key shayach:v1, written only after they change something), and "hide widget" uses sessionStorage (key shayach:hidden) for the current visit. The open panel card is kept in sessionStorage (key shayach:card) for the visit. Read aloud uses the browser's own speech engine.
  • Footprint: widget.js is under 7 KB gzip (a size budget enforced in the test suite) and does not block rendering. Dragging adds no page listeners while idle: one keydown listener exists only during a drag. The panel code (app.js, under 30 KB gzip) loads only when a visitor opens the panel or has saved adjustments. The widget renders in Shadow DOM, adds no global besides the site's own window.A11yConfig, and removes everything it added when an adjustment is turned off or reset.
  • Performance: measured in Chromium on a desktop computer, on a test page with 5,000 paragraphs: enlarging text applies in about 40 ms and dark contrast in about 16 ms, and loading the widget causes no task over 50 ms. When that page adds 50 paragraphs every 100 ms while text is enlarged, the widget adds about 8 ms of work per batch to scale them. Slower devices take longer.

Versioning and updates

  • Sites load the widget from a major-version URL (/v1/). Every 1.x.y release, including legal updates, reaches all sites automatically within minutes (files under /v1/ are cached for 5 minutes).
  • 1.x never contains a breaking change. Renaming or removing a config field, or changing the script URL, only ever ships as a new major version (/v2/), which a site adopts by changing its script URL. One exception: icon.image was removed in 1.6.0, before any site had installed the widget with it (see the changelog); a config that still sets it gets a warning and the regular icon.
  • Every release is listed in CHANGELOG.md (Keep a Changelog 1.1.0, Semantic Versioning). Entries marked [Action required] tell site owners exactly what to change; entries marked [Legal] come from a change in Israeli law or standards and link the official source. Entries without [Action required] need nothing from the site.
  • Machine-readable changelog: https://shayach.co.il/v1/changelog.json, generated from CHANGELOG.md at build time: { "versions": [...] }, newest first (starting with Unreleased), each with version, date and sections (such as Added, Changed, Fixed), each entry with text, legal, actionRequired and links. A withdrawn version has yanked: true.
  • Deployed build: https://shayach.co.il/v1/build.json gives the deployed version and commit.
  • A summary for AI agents is published at https://shayach.co.il/llms.txt.

License

Apache License 2.0. Anyone may use, fork and modify this project, including commercially. Redistributions and derivative works must keep the attribution in NOTICE.