Accessibility · Component reference
Image descriptions
Find the descriptions already authored for images and keep informative media in view.
Try the live exampleResearch and our take
Our recorded research compared reader preferences and accessibility tools across several projects. For this component, these observations informed Starlit’s approach:
- Royal Access: Typography, visual preferences and access to accessibility information. Our decision: Provide an accessible help and local report component. Keep content repair in author workflows.
- WP Accessibility: Skip destinations, focus visibility, image descriptions and controls for presentation. Our decision: Keep native navigation semantics and display authored image descriptions accurately.
Starlit wrote the shared controller, scoped content adapters and component modules presented here. The referenced widgets are research sources, not browser dependencies. The component also draws on interface work in EvansWiki and PayCal.
Design reasoning
Make authored image descriptions easier to find while preserving informative media.
- Start with the person’s task and the barrier they may encounter.
- Use with deliberately written alt text, captions and optional longer descriptions. Mark only genuinely decorative images as decorative.
- Keep the ordinary content and action usable before adding enhancement.
- Inspect the descriptions, follow an image destination, then hide decoration. Check that an informative image and its caption remain available.
Usage pattern
Use with deliberately written alt text, captions and optional longer descriptions. Mark only genuinely decorative images as decorative.
Impact and tradeoffs
A description list can reveal information that is hard to inspect visually. Automatically invented descriptions may mislead.
Accessibility solutions
This component reads authored descriptions; it does not generate them. Hiding decorative media must leave informative images intact.
Build accessible source content first. Personal preferences supplement semantic HTML, useful names, keyboard access and visible focus.
HTML
A concrete usage example. Host these assets on your own origin if you adapt the pattern. Read the source and credits before reuse.
<link rel="stylesheet" href="/open-ui/accessibility/shared/content.css">
<script type="module"
src="/open-ui/accessibility/components/image-descriptions/index.js"></script>
<article id="reading" data-sdou-a11y-content data-default-theme="dark">
<div data-sdou-a11y-prose>
<h2 id="begin">Begin with a clear purpose</h2>
<p>Readable text and useful controls belong together.</p>
<h2 id="details">Consider the details</h2>
<p>The page should remain usable before any enhancement loads.</p>
<figure>
<img src="/open-ui/brand/gallery/0.svg"
alt="An abstract geometric study in navy, cream and gold"
aria-describedby="image-context">
<figcaption id="image-context">A decorative composition used to explore the studio palette.</figcaption>
</figure>
</div>
</article>
<sdou-image-descriptions target="#reading"
storage-key="example.image-descriptions.v1"></sdou-image-descriptions>CSS
This is the component stylesheet used inside its Shadow DOM. The shared ui.css supplies its base controls and content.css adapts the reading region. An empty component stylesheet deliberately inherits the shared UI.
.image-list {
display: grid;
gap: 16px;
}
.image-description {
padding: 20px;
border: 1px solid var(--sdou-ax-line);
border-radius: 12px;
background: var(--sdou-ax-bg);
}
.image-description h3 {
font-size: 1.15rem;
}
.image-description p {
font-size: 0.9375rem;
color: var(--sdou-ax-muted);
}
JavaScript
This is the actual component module used by the live example. Imports, the base class and shared preferences are dependencies. Keep the component folder and shared folder in their relative positions.
import { A11yComponent, toggle } from "../../shared/base.js?v=sdou20261029";
class ImageDescriptions extends A11yComponent {
static styles = new URL("./component.css?v=sdou20261029", import.meta.url).href;
render() {
this.mount(
"Keep the picture in context.",
"Explore descriptions the author has provided. Informative images remain available.",
toggle(
"hideDecorative",
"Hide decorative images",
"Only images marked decorative are hidden.",
) +
'<div class="image-list" data-images></div><button type="button" class="button secondary" data-refresh>Refresh image list</button>',
);
}
initialize() {
this.renderImages();
this.on(this.q("[data-refresh]"), "click", () => {
this.renderImages();
this.say("Image descriptions refreshed.");
});
}
renderImages() {
const container = this.q("[data-images]");
container.replaceChildren();
const images = [...this.target.querySelectorAll("img")];
if (!images.length) {
const p = document.createElement("p");
p.className = "hint";
p.textContent = "There are no images in this reading area.";
container.append(p);
return;
}
images.forEach((image, i) => {
const card = document.createElement("article");
card.className = "image-description";
const label = document.createElement("h3");
label.textContent = "Image " + (i + 1);
const p = document.createElement("p");
const alt = image.getAttribute("alt");
p.textContent =
alt === null
? "No description was provided for this image."
: alt === ""
? "Marked decorative. It has no information needed to read the page."
: alt;
card.append(label, p);
const described = image.getAttribute("aria-describedby");
if (described) {
const details = described
.split(/\s+/)
.map((id) => document.getElementById(id)?.textContent || "")
.join(" ")
.trim();
if (details) {
const detail = document.createElement("p");
detail.textContent = details;
card.append(detail);
}
}
if (alt) {
const button = document.createElement("button");
button.type = "button";
button.className = "button secondary";
button.textContent = "Go to image " + (i + 1);
button.addEventListener(
"click",
() => {
const figure = image.closest("figure") || image;
figure.tabIndex = -1;
figure.focus({ preventScroll: true });
figure.scrollIntoView({ behavior: "auto", block: "center" });
},
{ signal: this.abort.signal },
);
card.append(button);
}
container.append(card);
});
}
}
customElements.define("sdou-image-descriptions", ImageDescriptions);
PHP integration
In your own application, PHP can render this HTML on the server. Here, the example is escaped text only. The library has no PHP runner or execution endpoint. The browser owns the interaction.
<?php
// Server render trusted structure and escape authored text.
// PHP does not implement or save these browser preferences.
declare(strict_types=1);
$title = 'A readable article';
$paragraph = 'Your authored content goes here.';
$e = static fn (string $value): string =>
htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
?>
<link rel="stylesheet" href="/open-ui/accessibility/shared/content.css">
<script type="module"
src="/open-ui/accessibility/components/image-descriptions/index.js"></script>
<article id="reading" data-sdou-a11y-content data-default-theme="dark">
<div data-sdou-a11y-prose>
<h2 id="begin"><?= $e($title) ?></h2>
<p><?= $e($paragraph) ?></p>
</div>
</article>
<sdou-image-descriptions target="#reading"
storage-key="example.image-descriptions.v1"></sdou-image-descriptions>
API and dependencies
- Element
sdou-image-descriptions- target
- A selector for the content region. Use a unique ID. Components sharing a target share its controller.
- storage-key
- A stable key for this project. Use the same key for controls targeting the same region. Preferences stay in browser storage when available.
- Content
data-sdou-a11y-contentmarks the target;data-sdou-a11y-proseidentifies reading copy. Usedata-default-theme="dark"for a dark initial and reset preference.- Event
sdou:a11y-changeis dispatched on the target withpreferences,paletteandreducedMotionin its detail.- Dependencies
- Modern JavaScript modules, custom elements, Shadow DOM and native form controls. Dialog, speech, clipboard and fullscreen support depend on the feature and browser.
Read shared/base.js · Read the preference controller · Read the content adapter · Read the shared control styles
Testing and limitations
Inspect the descriptions, follow an image destination, then hide decoration. Check that an informative image and its caption remain available.
The original library validation records automated rule checks, 320px reflow, enlarged text, text spacing, state persistence and relevant functional checks. Unresolved automated Shadow DOM reference checks are recorded separately from browser dialog checks. These historical results are not a new audit of every integration.
Production review still needs assistive technology, touch, zoom, custom styles and representative content. See our testing scope and known gaps. Report a barrier to Starlit.