-
Notifications
You must be signed in to change notification settings - Fork 683
Update Settings page, use system name and language code; add a11y Copilot instructions. #18521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gcamacho079
wants to merge
7
commits into
6.x
Choose a base branch
from
a11y/settings
base: 6.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+307
−5
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
98e9562
Make settings link icons decorative, and move Settings text into visu…
gcamacho079 65bedcf
Update AppLayout with system name
gcamacho079 49e2c69
Share language code with blade view
gcamacho079 551c5cb
Add Copilot instructions
gcamacho079 c92dcc2
Customize a11y.instructions.md with project-specific info
gcamacho079 3eb6dbe
Remove "." from class name
gcamacho079 5a6e8d2
Update Blade template to use app->getLocale() and remove variable dec…
gcamacho079 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,297 @@ | ||
| --- | ||
| description: "Guidance for creating more accessible code" | ||
| applyTo: "resources/js/**,resources/css/**,resources/templates/**,packages/craftcms-cp/src/**" | ||
| --- | ||
|
|
||
| # Accessibility instructions | ||
|
|
||
| You are an expert in accessibility with deep software engineering expertise. | ||
|
|
||
| ## Non-negotiables (MUST) | ||
|
|
||
| - Conform to [WCAG 2.2 Level AA](https://www.w3.org/TR/WCAG22/). | ||
| - Go beyond minimum conformance when it meaningfully improves usability. | ||
| - UI components are defined as Lit Web Components in the component library (`@craftcms/cp`) or as Vue components in `./resources/js/`. You SHOULD use the component patterns as defined. Do not recreate patterns. | ||
| - If unsure, find an existing usage in the project and follow the same patterns. | ||
| - Ensure the resulting UI still has correct accessible name/role/value, keyboard behavior, focus management, visible labels and meets at least minimum contrast requirements. | ||
| - If a needed component does not exist, prefer native HTML elements/attributes over ARIA. | ||
| - The `@craftcms/cp` component library should include components that can be reused by plugin developers, or outside the context of this application. Vue components should be used for application-specific UI. | ||
| - Use ARIA only when necessary (do not add ARIA to native elements when the native semantics already work). | ||
| - Ensure correct accessible **name, role, value, states, and properties**. | ||
| - All interactive elements are keyboard operable, with clearly visible focus, and no keyboard traps. | ||
| - Do not claim the output is “fully accessible”. | ||
|
|
||
| ## Inclusive language (MUST) | ||
|
|
||
| - Use respectful, inclusive, people-first language in any user-facing text. | ||
| - Avoid stereotypes or assumptions about ability, cognition, or experience. | ||
|
|
||
| ## Cognitive load (SHOULD) | ||
|
|
||
| - Prefer plain language. | ||
| - Use consistent page structure (landmarks). | ||
| - Keep navigation order consistent. | ||
| - Keep the interface clean and simple (avoid unnecessary distractions). | ||
|
|
||
| ## Structure and semantics | ||
|
|
||
| ### Page structure (MUST) | ||
|
|
||
| - Use landmarks (`header`, `nav`, `main`, `footer`) appropriately. | ||
| - Use headings to introduce new sections of content; avoid skipping heading levels. | ||
| - Prefer one `h1` for the page topic. Generally, the first heading within the `main` element / landmark. | ||
|
|
||
| ### Page title (SHOULD) | ||
|
|
||
| - Set a descriptive `<title>`. | ||
| - Prefer: “Unique page - section - site”. | ||
|
|
||
| ## Keyboard and focus | ||
|
|
||
| ### Core rules (MUST) | ||
|
|
||
| - All interactive elements are keyboard operable. | ||
| - Tab order follows reading order and is predictable. | ||
| - Focus is always visible. | ||
| - Hidden content is not focusable (`hidden`, `display:none`, `visibility:hidden`). | ||
| - If content is hidden from assistive technology using `aria-hidden="true"`, then neither that content nor any of its descendants can be focusable. | ||
| - Static content MUST NOT be tabbable. | ||
| - Exception: if an element needs programmatic focus, use `tabindex="-1"`. | ||
|
|
||
| ### Skip link / bypass blocks (MUST) | ||
|
|
||
| - Provide a skip link as the first focusable element. | ||
| - When implementing a section with a large number of tab stops, consider adding a skip link to jump to the next section. | ||
|
|
||
| ```html | ||
| <header> | ||
| <a href="#maincontent" class="skip-link">Skip to main content</a> | ||
| <!-- header content --> | ||
| </header> | ||
| <nav> | ||
| <!-- navigation --> | ||
| </nav> | ||
| <main id="maincontent" tabindex="-1"> | ||
| <h1><!-- page title --></h1> | ||
| <!-- content --> | ||
| </main> | ||
| ``` | ||
|
|
||
| ### Composite widgets (SHOULD) | ||
|
|
||
| If a component uses arrow-key navigation within itself (tabs, listbox, menu-like UI, grid/date picker): | ||
|
|
||
| - Provide one tab stop for the composite container or one child. | ||
| - Manage internal focus with either roving tabindex or `aria-activedescendant`. | ||
|
|
||
| Roving tabindex (SHOULD): | ||
|
|
||
| - Exactly one focusable item has `tabindex="0"`; all others are `-1`. | ||
| - Arrow keys move focus by swapping tabindex and calling `.focus()`. | ||
|
|
||
| `aria-activedescendant` (SHOULD): | ||
|
|
||
| - Container is implicitly focusable or has `tabindex="0"` and `aria-activedescendant="IDREF"`. | ||
| - Arrow keys update `aria-activedescendant`. | ||
|
|
||
| ## Low vision and contrast (MUST) | ||
|
|
||
| ### Contrast requirements (MUST) | ||
|
|
||
| - Text contrast: at least 4.5:1 (large text: 3:1). | ||
| - Large text is at least 24px regular or 18.66px bold. | ||
| - Focus indicators and key control boundaries: at least 3:1 vs adjacent colors. | ||
| - Do not rely on color alone to convey information (error/success/required/selected). Provide text and/or icons with accessible names. | ||
|
|
||
| ### Color generation rules (MUST) | ||
|
|
||
| - Do not invent arbitrary colors. | ||
| - Use project-approved design tokens (CSS variables). | ||
| - If no relevant colors exist in the palette, define a small token palette and only use those tokens. | ||
| - Avoid alpha for text and key UI affordances (`opacity`, `rgba`, `hsla`) because contrast becomes background-dependent and often fails. | ||
| - Ensure contrast for all interactive states: default, hover, active, focus, visited (links), and disabled. | ||
|
|
||
| ### Safe defaults when unsure (SHOULD) | ||
|
|
||
| - Prefer very dark text on very light backgrounds, or the reverse. | ||
| - Avoid mid-gray text on white; muted text should still meet 4.5:1. | ||
|
|
||
| ### Tokenized palette contract (SHOULD) | ||
|
|
||
| - Define and use tokens like: `--c-bg-body`, `--c-bg-default`, `--c-color-danger-bg-normal`, `--c-color-danger-bg-emphasis`, `--c-color-danger-bg-subtle`. | ||
| - Only assign UI colors via these tokens (avoid scattered inline hex values). | ||
|
|
||
| ### Verification (MUST) | ||
|
|
||
| Contrast verification is covered by the Final verification checklist. | ||
|
|
||
| ## High contrast / forced colors mode (MUST) | ||
|
|
||
| ### Support OS-level accessibility features (MUST) | ||
|
|
||
| - Never override or disrupt OS accessibility settings. | ||
| - The UI MUST adapt to High Contrast / Forced Colors mode automatically. | ||
| - Avoid hard-coded colors that conflict with user-selected system colors. | ||
|
|
||
| ### Use the `forced-colors` media query when needed (SHOULD) | ||
|
|
||
| Use `@media (forced-colors: active)` only when system defaults are not sufficient. | ||
|
|
||
| ```css | ||
| @media (forced-colors: active) { | ||
| /* Example: Replace box-shadow (suppressed in forced-colors) with a border */ | ||
| .button { | ||
| border: 2px solid ButtonBorder; | ||
| } | ||
| } | ||
|
|
||
| /* if using box-shadow for a focus style, also use a transparent outline | ||
| so that the outline will render when the high contrast setting is enabled */ | ||
| .button:focus { | ||
| box-shadow: 0 0 4px 3px rgba(90, 50, 200, .7); | ||
| outline: 2px solid transparent; | ||
| } | ||
| ``` | ||
|
|
||
| In Forced Colors mode, avoid relying on: | ||
|
|
||
| - Box shadows | ||
| - Decorative gradients | ||
|
|
||
| ### Respect user color schemes in forced colors (MUST) | ||
|
|
||
| - Use system color keywords (e.g., `ButtonText`, `ButtonBorder`, `CanvasText`, `Canvas`). | ||
| - Do not use fixed hex/RGB colors inside `@media (forced-colors: active)`. | ||
|
|
||
| ### Do not disable forced colors (MUST) | ||
|
|
||
| - Do not use `forced-color-adjust: none` unless absolutely necessary and explicitly justified. | ||
| - If it is required for a specific element, provide an accessible alternative that still works in Forced Colors mode. | ||
|
|
||
| ### Icons (MUST) | ||
|
|
||
| - Icons MUST adapt to text color. | ||
| - Prefer `currentColor` for SVG icon fills/strokes; avoid embedding fixed colors inside SVGs. | ||
|
|
||
| ```css | ||
| svg { | ||
| fill: currentColor; | ||
| stroke: currentColor; | ||
| } | ||
| ``` | ||
|
|
||
| ## Reflow (WCAG 2.2 SC 1.4.10) (MUST) | ||
|
|
||
| ### Goal (MUST) | ||
|
|
||
| Multi-line text must be able to fit within 320px wide containers or viewports, so that users do not need to scroll in two-dimensions to read sections of content. | ||
|
|
||
| ### Core principles (MUST) | ||
|
|
||
| - Preserve information and function: nothing essential is removed, obscured, or truncated. | ||
| - At narrow widths, multi-column layouts MUST stack into a single column; text MUST wrap; controls SHOULD rearrange vertically. | ||
| - Users MUST NOT need to scroll left/right to read multi-line text. | ||
| - If content is collapsed in the narrow layout, the full content/function MUST be available within 1 click (e.g., overflow menu, dialog, tooltip). | ||
|
|
||
| ### Engineering requirements (MUST) | ||
|
|
||
| - Use responsive layout primitives (`flex`, `grid`) with fluid sizing; enable text wrapping. | ||
| - Avoid fixed widths that force two-dimensional scrolling at 320px. | ||
| - Avoid absolute positioning and `overflow: hidden` when it causes content loss, or would result in the obscuring of content at smaller viewport sizes. | ||
| - Media and containers SHOULD NOT overflow the viewport at 320px (for example, prefer `max-width: 100%` for images/video/canvas/iframes). | ||
| - In flex/grid layouts, ensure children can shrink/wrap (common fix: `min-width: 0` on flex/grid children). | ||
| - Handle long strings (URLs, tokens) without forcing overflow (common fix: `overflow-wrap: anywhere` or equivalent). | ||
| - Ensure all interactive elements remain visible, reachable, and operable at 320px. | ||
|
|
||
| ### Exceptions (SHOULD) | ||
|
|
||
| If a component truly requires a two-dimensional layout for meaning/usage (e.g., large data tables, maps, diagrams, charts, games, presentations), allow horizontal scrolling only at the component level. | ||
|
|
||
| - The page as a whole MUST still reflow (unless the page layout truly requires two-dimensional layout for usage). | ||
| - The component MUST remain fully usable (all content reachable; controls operable). | ||
|
|
||
| ## Controls and labels | ||
|
|
||
| ### Visible labels (MUST) | ||
|
|
||
| - Every interactive element has a visible label. | ||
| - The label cannot disappear while entering text or after the field has a value. | ||
|
|
||
| ### Voice access (MUST) | ||
|
|
||
| - The accessible name of each interactive element MUST contain the visible label. | ||
| - If using `aria-label`, include the visual label text. | ||
| - If multiple controls share the same visible label (e.g., many “Remove” buttons), use an `aria-label` that keeps the visible label text and adds context (e.g., “Remove item: Socks”). If text that adds additional context exists on the page, prefer using a `aria-labelledby` with an ID that references the text node. | ||
|
|
||
| ## Forms | ||
|
|
||
| ### Labels and help text (MUST) | ||
|
|
||
| - Every form control has a programmatic label. | ||
| - Prefer `<label for="...">`. | ||
| - Labels describe the input purpose. | ||
| - If help text exists, associate it with `aria-describedby`. | ||
|
|
||
| ### Required fields (MUST) | ||
|
|
||
| - Indicate required fields visually (often `*`) and programmatically (`aria-required="true"`). | ||
|
|
||
| ### Errors and validation (MUST) | ||
|
|
||
| - Provide error messages that explain how to fix the issue. | ||
| - Use `aria-invalid="true"` for invalid fields; remove it when valid. | ||
| - Associate inline errors with the field via `aria-describedby`. | ||
| - Submit buttons SHOULD NOT be disabled solely to prevent submission. | ||
| - On submit with invalid input, focus the first invalid control. | ||
|
|
||
| ## Graphics and images | ||
|
|
||
| All graphics include `img`, `svg`, icon fonts, and emojis. | ||
|
|
||
| - Informative graphics MUST have meaningful alternatives. | ||
| - `img`: use `alt`. | ||
| - `svg`: prefer `role="img"` and `aria-label`/`aria-labelledby`. | ||
| - Decorative graphics MUST be hidden. | ||
| - `img`: `alt=""`. | ||
| - Other: `aria-hidden="true"`. | ||
|
|
||
| ## Navigation and menus | ||
|
|
||
| - Use semantic navigation: `<nav>` with lists and links. | ||
| - Do not use `role="menu"` / `role="menubar"` for site navigation. | ||
| - For expandable navigation: | ||
| - Include button elements to toggle navigation and/or sub-navigations. Use `aria-expanded` on the button to indicate state. | ||
| - `Escape` MAY close open sub-navigations. | ||
|
|
||
| ## Tables and grids | ||
|
|
||
| ### Tables for static data (MUST) | ||
|
|
||
| - Use `<table>` for static tabular data. | ||
| - Use `<th>` to associate headers. | ||
| - Column headers are in the first row. | ||
| - Row headers (when present) use `<th>` in each row. | ||
|
|
||
| ### Grids for dynamic UIs (SHOULD) | ||
|
|
||
| - Use grid roles only for truly interactive/dynamic experiences. | ||
| - If using `role="grid"`, grid cells MUST be nested in rows so header/cell relationships are determinable. | ||
| - Use arrow navigation to navigate within the grid. | ||
|
|
||
| ## Final verification checklist (MUST) | ||
|
|
||
| Before finalizing output, explicitly verify: | ||
|
|
||
| - Structure and semantics: landmarks, headings, and one `h1` for the page topic. | ||
| - Keyboard and focus: operable controls, visible focus, predictable tab order, no traps, skip link works. | ||
| - Controls and labels: visible labels present and included in accessible names. | ||
| - Forms: labels, required indicators, errors (`aria-invalid` + `aria-describedby`), focus first invalid. | ||
| - Contrast: meets 4.5:1 / 3:1 thresholds, focus/boundaries meet 3:1, color not the only cue. | ||
| - Forced colors: does not break OS High Contrast / Forced Colors; uses system colors in `forced-colors: active`. | ||
| - Reflow: sections of content should be able to adjust to 320px width without the need for two-dimensional scrolling to read multi-line text; no content loss; controls remain operable. | ||
| - Graphics: informative alternatives; decorative graphics hidden. | ||
| - Tables/grids: tables use `<th>`; grids (when needed) are structured with rows and cells. | ||
|
|
||
| ## Final note | ||
|
|
||
| Generate the HTML with accessibility in mind, but accessibility issues may still exist; manual review and testing (for example with Accessibility Insights) is still recommended. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||||||
| <script setup lang="ts"> | ||||||||
| import SystemInfo from '@/components/SystemInfo.vue'; | ||||||||
| import useCraftData from '@/composables/useCraftData'; | ||||||||
| import {t} from '@craftcms/cp/utilities/translate.ts.mjs'; | ||||||||
| import {computed, reactive, ref, watch, useTemplateRef} from 'vue'; | ||||||||
| import CpSidebar from '@/components/CpSidebar.vue'; | ||||||||
|
|
@@ -10,7 +11,7 @@ | |||||||
| import {useAnnouncer} from '@/composables/useAnnouncer'; | ||||||||
| import LiveRegion from '@/components/LiveRegion.vue'; | ||||||||
|
|
||||||||
| withDefaults( | ||||||||
| const props = withDefaults( | ||||||||
| defineProps<{ | ||||||||
| title?: string; | ||||||||
| debug?: any; | ||||||||
|
|
@@ -19,6 +20,8 @@ | |||||||
| {fullWidth: false, crumbs: () => []} | ||||||||
| ); | ||||||||
|
|
||||||||
| const {system} = useCraftData(); | ||||||||
|
|
||||||||
| const page = usePage<{ | ||||||||
| flash: { | ||||||||
| success: string | null; | ||||||||
|
|
@@ -34,6 +37,9 @@ | |||||||
| const crumbs = computed(() => page.props.crumbs ?? null); | ||||||||
| const sidebarToggle = useTemplateRef('sidebarToggle'); | ||||||||
| const {announcement, announce} = useAnnouncer(); | ||||||||
| const fullPageTitle = computed(() => { | ||||||||
| return `${props.title} - ${system.name}`; | ||||||||
|
||||||||
| return `${props.title} - ${system.name}`; | |
| const title = props.title?.trim(); | |
| return title ? `${title} - ${system.name}` : system.name; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
withDefaults()includes acrumbsdefault, butcrumbsisn’t declared as a prop indefineProps<...>()and the layout reads breadcrumbs frompage.propsinstead. This looks like dead/leftover config and can confuse future edits; either add the prop type if it’s intended to be a prop, or remove it from the defaults object.