Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 2.3.1 — 2026-10-23

- Provide runnable Next.js App Router consumer example in `examples/next-app/` with Server Component layout, client boundaries, and route transitions (closes #39).
- Add comprehensive Content Security Policy (CSP) guidance in `docs/CSP.md` for Next.js, Vite, and strict environments.
- Enhance Vite examples with light and dark theme switching and client-side view transition simulation.
- Verify React 18/19 Server-Side Rendering (SSR) in `tests/consumerSsr.test.tsx` ensuring zero server browser-global access and no hydration divergence.
- Add browser integration regression for Next.js App Router client-side Link transitions preserving accessibility preferences across routes.

## 2.3.0 — 2026-10-22

- Introduce accessible feature card and control primitives (`FeatureCard`, `ToggleControl`, `ChoiceControl`, `StepperControl`) (closes #36).
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export function AccessibilitySettings() {
```

See [installation and Next.js](docs/INSTALLATION.md) for App Router and Pages
Router examples.
Router examples, runnable code in [`examples/next-app/`](examples/next-app/), and [strict CSP guidance](docs/CSP.md).

## API and limitations

Expand All @@ -274,6 +274,7 @@ See [troubleshooting](docs/TROUBLESHOOTING.md) before filing an issue.
- [API reference](docs/API.md)
- [Feature behavior and bounds](docs/FEATURES.md)
- [Installation and Next.js](docs/INSTALLATION.md)
- [Content Security Policy (CSP) guidance](docs/CSP.md)
- [Custom features](docs/CUSTOM_FEATURES.md)
- [Migration from v1 and development baselines](docs/MIGRATION.md)
- [Architecture and lifecycle decisions](docs/ARCHITECTURE.md)
Expand Down
16 changes: 8 additions & 8 deletions docs/BUILD_AND_RELEASE_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ completed. Closing a child issue does not replace the milestone exit review.

### M1 — Safe core and accessible widget

- [ ] [A11Y-101 — Typed preferences registry and state reducer #32](https://github.com/prabothcharith/accessibility-react/issues/32) — Implement typed preference definitions, validation, defaults, stable feature IDs and reducer logic for effective settings and profile overrides.
- [ ] [A11Y-102 — Scoped effect manager and cleanup lifecycle #33](https://github.com/prabothcharith/accessibility-react/issues/33) — Implement provider-owned idempotent effect apply/dispose lifecycle with scoped styles, listener cleanup, widget exclusion and Strict Mode safety.
- [ ] [A11Y-103 — Versioned storage, reset, migration and no-storage fallback #34](https://github.com/prabothcharith/accessibility-react/issues/34) — Implement versioned `none`/session/local storage, migrations, validation, reset of owned keys, blocked-storage handling and temporary no-remember mode.
- [ ] [A11Y-104 — Accessible launcher and non-modal widget panel #35](https://github.com/prabothcharith/accessibility-react/issues/35) — Build the keyboard and screen-reader accessible non-modal launcher/panel with visible focus, Escape, focus restoration, responsive layout and stable scrolling.
- [ ] [A11Y-105 — Feature cards and state/value UI #36](https://github.com/prabothcharith/accessibility-react/issues/36) — Build reusable native feature controls for toggles, choices, sliders, search, values, statuses, errors, unsupported states and profile previews.
- [ ] [A11Y-106 — Position, oversized controls, statement link and help #37](https://github.com/prabothcharith/accessibility-react/issues/37) — Implement position and non-drag movement, oversized controls, safe-area actions, conditional statement link and help while reconciling existing positioning work.
- [ ] [A11Y-107 — Base locale infrastructure and English strings #38](https://github.com/prabothcharith/accessibility-react/issues/38) — Add typed English locale strings, configured/html-language fallback, BCP 47 and RTL handling, announcements and safe missing-key behavior.
- [ ] [A11Y-108 — Vite and Next.js App Router consumer integrations #39](https://github.com/prabothcharith/accessibility-react/issues/39) — Provide Vite and Next.js App Router consumers and verify hydration, portals, React peer versions, CSS, declarations, themes and CSP guidance.
- [x] [A11Y-101 — Typed preferences registry and state reducer #32](https://github.com/prabothcharith/accessibility-react/issues/32) — Implement typed preference definitions, validation, defaults, stable feature IDs and reducer logic for effective settings and profile overrides.
- [x] [A11Y-102 — Scoped effect manager and cleanup lifecycle #33](https://github.com/prabothcharith/accessibility-react/issues/33) — Implement provider-owned idempotent effect apply/dispose lifecycle with scoped styles, listener cleanup, widget exclusion and Strict Mode safety.
- [x] [A11Y-103 — Versioned storage, reset, migration and no-storage fallback #34](https://github.com/prabothcharith/accessibility-react/issues/34) — Implement versioned `none`/session/local storage, migrations, validation, reset of owned keys, blocked-storage handling and temporary no-remember mode.
- [x] [A11Y-104 — Accessible launcher and non-modal widget panel #35](https://github.com/prabothcharith/accessibility-react/issues/35) — Build the keyboard and screen-reader accessible non-modal launcher/panel with visible focus, Escape, focus restoration, responsive layout and stable scrolling.
- [x] [A11Y-105 — Feature cards and state/value UI #36](https://github.com/prabothcharith/accessibility-react/issues/36) — Build reusable native feature controls for toggles, choices, sliders, search, values, statuses, errors, unsupported states and profile previews.
- [x] [A11Y-106 — Position, oversized controls, statement link and help #37](https://github.com/prabothcharith/accessibility-react/issues/37) — Implement position and non-drag movement, oversized controls, safe-area actions, conditional statement link and help while reconciling existing positioning work.
- [x] [A11Y-107 — Base locale infrastructure and English strings #38](https://github.com/prabothcharith/accessibility-react/issues/38) — Add typed English locale strings, configured/html-language fallback, BCP 47 and RTL handling, announcements and safe missing-key behavior.
- [x] [A11Y-108 — Vite and Next.js App Router consumer integrations #39](https://github.com/prabothcharith/accessibility-react/issues/39) — Provide Vite and Next.js App Router consumers and verify hydration, portals, React peer versions, CSS, declarations, themes and CSP guidance.
- [ ] [A11Y-109 — CI for typecheck, lint, unit, axe and browser smoke #40](https://github.com/prabothcharith/accessibility-react/issues/40) — Establish CI for typecheck, lint, unit/integration, relevant axe checks, packed browser smoke and supported browser jobs without publication credentials.

### M2 — Stable visual and typographic features
Expand Down
185 changes: 185 additions & 0 deletions docs/CSP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Content Security Policy (CSP) Guidance

This guide explains how `accessibility-plugin-react` operates under Content Security Policy (CSP), the specific directives involved, and copy-pasteable recipes for strict environments in Next.js, Vite, and static deployments.

---

## 1. How the Library Works Under CSP

`accessibility-plugin-react` is designed to be lightweight, modular, and dependency-free. However, because it dynamically adapts host pages according to user accessibility preferences, several CSP directives are relevant:

### A. Scoped Stylesheet Injection (`style-src`)

- **Default behavior:** Importing `accessibility-plugin-react` automatically injects its scoped stylesheet (`.arap-root { ... }`) into `document.head` via a runtime `<style>` element.
- **Strict CSP (`style-src 'self'` without `'unsafe-inline'`):** If your CSP policy disallows inline `<style>` tags, you should import the standalone stylesheet directly in your build or application entry point instead of relying on runtime injection:
```tsx
// app/layout.tsx (Next.js) or main.tsx (Vite)
import "accessibility-plugin-react/style.css";
```
Because `style.css` is processed by your bundler (webpack, Vite, Turbopack) as a static asset, it is served from `'self'` and requires no inline script or style permissions.

### B. Element Style Mutations (`style-src-attr`)

- **How effects are applied:** The scoped effect manager applies display preferences (such as font scaling, line height, letter spacing, contrast, saturation, and color filters) directly to DOM elements via the CSSOM (e.g., `element.style.fontSize = "120%"`).
- **CSP Level 3 considerations:** In modern browsers enforcing CSP Level 3, mutations to an element's `.style` attribute are governed by `style-src-attr`.
- **Recommended directive:**
```text
style-src-attr 'unsafe-inline';
```
Note that `style-src-attr 'unsafe-inline'` does not allow arbitrary `<style>` tags; it only permits direct style property mutations on DOM elements.

### C. Big Cursor and High-Contrast Overlays (`img-src`)

- **How cursor enlargement works:** The `BigCursorController` dynamically generates an SVG data URI cursor (`data:image/svg+xml,...`) to provide high-visibility, high-contrast pointer cursors without requiring external network requests.
- **Recommended directive:**
```text
img-src 'self' data:;
```
If `data:` is omitted from `img-src`, the browser will fall back to native system cursors.

---

## 2. Next.js App Router Strict CSP Recipe

In Next.js App Router, you can set CSP headers in `next.config.mjs` or dynamically in `middleware.ts` with nonces.

### Option 1: Static Headers via `next.config.mjs` (Recommended)

```javascript
// next.config.mjs
/** @type {import('next').NextConfig} */
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
style-src-attr 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
`
.replace(/\s{2,}/g, " ")
.trim();

const nextConfig = {
reactStrictMode: true,
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Content-Security-Policy",
value: cspHeader,
},
],
},
];
},
};

export default nextConfig;
```

When using `'self'` without `'unsafe-inline'` in `style-src`, ensure you import the stylesheet in `app/layout.tsx`:

```tsx
// app/layout.tsx
import "accessibility-plugin-react/style.css";
```

### Option 2: Dynamic Nonce via `middleware.ts`

If your organization requires cryptographic nonces for script and style execution:

```typescript
// middleware.ts
import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
style-src-attr 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
base-uri 'self';
form-action 'self';
`
.replace(/\s{2,}/g, " ")
.trim();

const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set("Content-Security-Policy", cspHeader);

const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
response.headers.set("Content-Security-Policy", cspHeader);
return response;
}

export const config = {
matcher: [
{
source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
missing: [
{ type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" },
],
},
],
};
```

---

## 3. Vite / Single Page App Strict CSP Recipe

For Vite applications, configure CSP headers either through your production web server (nginx, Apache, Caddy, Cloudflare) or directly in `index.html`:

```html
<!-- index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
style-src-attr 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Accessible App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
```

---

## 4. Verification Checklist

| Requirement | Directive | Why It Is Needed |
| :---------------------------- | :-------------------------------------------------- | :------------------------------------------------------------------------------------------------------- |
| **Widget UI Styles** | `style-src 'self'` (with static `style.css` import) | Renders the Clean light widget panel, trigger, and control cards |
| **Dynamic Host Effects** | `style-src-attr 'unsafe-inline'` | Applies runtime font-size, line-height, and filter adjustments to host elements |
| **Big High-Contrast Cursor** | `img-src 'self' data:` | Allows SVG data URI pointer rendering |
| **No External Font Requests** | `font-src 'self'` | The plugin does not load external web fonts; all font-legibility features use local and system fallbacks |
| **No Network Calls** | No special `connect-src` | All preferences, state persistence, and effects operate 100% locally on the client |
8 changes: 7 additions & 1 deletion docs/INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export default function RootLayout({ children }: { children: ReactNode }) {

Mount the widget in the persistent root layout, not each route. Do not place document reads inside render. Do not disable SSR merely to mask hydration problems. Effects and portals appear on the client after hydration; a brief default-style paint can occur.

A complete runnable example is available in [`examples/next-app/`](../examples/next-app/).

## Content Security Policy (CSP)

For strict Content Security Policy environments (such as disabling inline styles or generating cryptographic nonces), see the dedicated [Content Security Policy Guidance](CSP.md) for full Next.js and Vite recipes.

## Next.js Pages Router

```tsx
Expand All @@ -87,7 +93,7 @@ export default function App({ Component, pageProps }: AppProps) {

Load library CSS after a host reset and load intentional widget overrides afterward. There is no library global preflight. Portals are independent of application layout and filter effects. Global host styles can still affect inherited properties or override selectors, especially with `!important`; inspect computed styles if controls differ.

Filters create a containing block for fixed host descendants. Test sticky/fixed headers, modals, menus, images and third-party overlays in your real application. Text measurement does not enter iframe documents or Shadow DOM. Hiding videos is visual only and is not a playback-control feature. See troubleshooting for CSP and font-loading considerations.
Filters create a containing block for fixed host descendants. Test sticky/fixed headers, modals, menus, images and third-party overlays in your real application. Text measurement does not enter iframe documents or Shadow DOM. Hiding videos is visual only and is not a playback-control feature. See [CSP guidance](CSP.md) and troubleshooting for CSP and font-loading considerations.

## Verified consumers

Expand Down
82 changes: 76 additions & 6 deletions examples/basic-usage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,91 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Accessibility Plugin — Basic Usage</title>
<title>React Accessibility Plugin — Basic Usage & Theme Demo</title>
<style>
:root {
--host-bg: #f9fafb;
--host-text: #111827;
--host-card: #ffffff;
--host-border: #e5e7eb;
--host-btn: #2563eb;
--host-btn-hover: #1d4ed8;
}
html.dark {
--host-bg: #0f172a;
--host-text: #f8fafc;
--host-card: #1e293b;
--host-border: #334155;
--host-btn: #3b82f6;
--host-btn-hover: #60a5fa;
}
body {
font-family: sans-serif;
padding: 40px;
font-family:
system-ui,
-apple-system,
sans-serif;
background-color: var(--host-bg);
color: var(--host-text);
padding: 40px 20px;
max-width: 800px;
margin: 0 auto;
transition:
background-color 0.2s ease,
color 0.2s ease;
}
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid var(--host-border);
}
.nav-tabs {
display: flex;
gap: 8px;
}
.tab-btn {
background: transparent;
border: 1px solid var(--host-border);
color: var(--host-text);
padding: 6px 14px;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
}
h1 {
color: #111827;
.tab-btn.active {
background-color: var(--host-btn);
color: #ffffff;
border-color: var(--host-btn);
}
.theme-btn {
background: var(--host-card);
border: 1px solid var(--host-border);
color: var(--host-text);
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
.card {
background-color: var(--host-card);
border: 1px solid var(--host-border);
border-radius: 8px;
padding: 24px;
margin-top: 16px;
}
h1,
h2 {
color: var(--host-text);
margin-top: 0;
}
p {
color: #4b5563;
line-height: 1.6;
}
a {
color: var(--host-btn);
}
</style>
</head>
<body>
Expand Down
Loading