WCAG 1.4.5 Images of Text — Testing Guide for Mobile & Web Apps
WCAG 1.4.5 (Images of Text) is a Level AA success criterion that requires any text presented as an image to also be available in a programmatic text form, unless the image is essential (e.g., a logo o
Understanding WCAG 1.4.5 Images of Text
WCAG 1.4.5 (Images of Text) is a Level AA success criterion that requires any text presented as an image to also be available in a programmatic text form, unless the image is essential (e.g., a logo or a decorative element). The intent is to ensure that users who rely on screen readers, text‑to‑speech, or custom styling can access the same information without loss of meaning or readability.
In practice, this means:
- If a visual element conveys readable characters (letters, numbers, symbols) that a user would need to read, those characters must also exist as actual text in the DOM (web) or accessibility hierarchy (mobile).
- Exceptions are limited to cases where the visual presentation is integral to the information being conveyed, such as a brand logo, a CAPTCHA that must remain an image, or a chart where the image itself is the data. Even then, alternative text must describe the purpose.
The criterion does not ban images of text entirely; it bans *unnecessary* images of text that could be replaced with real text while preserving the same visual appearance.
Who It Affects and Real‑World Impact
Users with Low Vision
People who enlarge text or apply high‑contrast modes rely on the ability to scale characters without pixelation. An image of text becomes blurry when magnified, making it unreadable.
Users Who Customize Styles
Users who replace fonts, adjust line spacing, or apply personal color schemes cannot do so when the text is baked into an image. The result is a loss of control over readability.
Screen‑Reader Users
If the image lacks appropriate alternative text, the screen reader announces nothing or a vague description, causing missed information. Even with alt text, the user cannot interact with the text (e.g., copy, search).
Cognitive and Language Users
Some users benefit from being able to select text to look up definitions or translate it. Images of text block that workflow.
Legal and Business Risks
In the EU, the European Accessibility Act (EAA) mandates WCAG 2.1 AA compliance for many digital products and services. In the United States, the Department of Justice has interpreted the ADA to require comparable accessibility. Non‑compliance can lead to complaints, litigation, and loss of market share.
Common Violations in Web Applications
Header Images Styled as Text
A frequent pattern is to use a PNG or SVG that contains a site’s tagline or navigation label, styled with CSS to look like a heading.
<!-- Violation -->
<h1><img src="welcome-banner.png" alt="Welcome to Our Store"></h1>
Buttons with Icon‑Only Labels
Designers sometimes replace the visible label with an icon that also encodes the word (e.g., a shopping‑cart icon that also says “Cart”).
<!-- Violation -->
<button aria-label="Cart">
<img src="cart-label.png" alt="">
</button>
Infographics with Embedded Data Labels
Charts that embed numeric values as part of the graphic force users to rely on visual perception.
<!-- Violation -->
<figure>
<img src="sales-chart.png" alt="Sales chart">
<figcaption>Q1 2024 performance</figcaption>
</figure>
Decorative Text Used for Branding
A stylized logotype that also contains the company name in a custom font. If the logotype is used elsewhere as a heading, it becomes a heading image, it violates the rule.
<!-- Violation (if used as heading) -->
<h2><img src="company-name-stylized.png" alt="Acme Corp"></h2>
Common Violations in Mobile Applications
Android: Bitmap Text in ImageViews
Developers sometimes place a static bitmap that contains a label (e.g., “Submit”) inside an ImageView and rely on the image’s content for meaning.
<!-- Violation -->
<ImageView
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/submit_label"
android:contentDescription="" />
iOS: UIImageView with Embedded Text
Similar to Android, a UIImageView may hold a rendered string that should be a UILabel.
// Violation
let labelImage = UIImage(named: "StartButton")
let imageView = UIImageView(image: labelImage)
imageView.accessibilityLabel = "" // missing description
Custom Controls That Paint Text
A custom view that draws text via Canvas (Android) or CoreGraphics (iOS) without exposing it through accessibility APIs.
// Android custom view – violation
@Override
protected void onDraw(Canvas canvas) {
canvas.drawText("Sign In", 10, 30, paint);
}
// iOS custom view – violation
override func draw(_ rect: CGRect) {
let text = "Sign In"
text.draw(at: CGPoint(x: 10, y: 30), withAttributes: [.font: UIFont.systemFont(ofSize: 16)])
}
Dialogs That Use Image‑Based Messages
Error dialogs that show an image containing the message text, with no accessible alternative.
<!-- Android dialog layout – violation -->
<ImageView
android:id="@+id/errorMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/error_text"
android:contentDescription="@string/error_desc" />
If the contentDescription merely repeats the image file name or is missing, the violation persists.
Manual Testing Procedure
Step 1: Identify Candidate Elements
- Scan the UI for any visual element that contains readable characters (letters, numbers, symbols).
- Look for headings, buttons, labels, navigation items, form placeholders, and any text that appears inside an image, SVG, or canvas.
Step 2: Determine If the Image Is Essential
Ask:
- Does removing the image and replacing it with plain text change the meaning or purpose?
- Is the image a logo, a CAPTCHA, or a chart where the visual representation is the data?
If the answer is *no* to essentiality, proceed to step 3.
Step 3: Verify Programmatic Text Exists
Web:
- Inspect the element in DevTools.
- Confirm that the visible characters are present in the DOM as text nodes, not solely as
alttext on an<img>. - If the text is only available via
aria-label,aria-labelledby, ortitle, verify that the label is concise and matches the visual text.
Android:
- Enable TalkBack.
- Focus on the element and listen to the spoken output.
- Use the Accessibility Scanner or Android Studio’s Layout Inspector to verify that a
contentDescription(ortextproperty) contains the exact visible string.
iOS:
- Turn on VoiceOver.
- Tap the element and listen to the spoken label.
- Use the Accessibility Inspector in Xcode to confirm that the
accessibilityLabel(oraccessibilityValue) matches the visible characters.
Step 4: Check Scalability and Custom Styling
- Zoom the page to 200 % (web) or increase the system font size (mobile).
- Verify that the text remains crisp and readable.
- Apply a high‑contrast theme or user‑defined CSS; ensure the text adapts.
Step 5: Document Findings
Record each violation with:
- Element selector or resource ID.
- Screenshot (if helpful).
- Current implementation (image source, missing alt/label).
- Recommended fix (replace with real text, provide appropriate alt/label, or mark as essential).
Automated Testing Tools and Techniques
Web‑Focused Automated Checks
| Tool / Library | Primary Mechanism | Strengths | Limitations |
|---|---|---|---|
| axe‑core (npm) | DOM inspection for img elements with missing or inadequate alt text; heuristic detection of text‑like images via contrast and OCR (optional plugin) | Integrates with unit, integration, and end‑to‑end test runners; CI‑friendly | May flag decorative images incorrectly; OCR‑based detection adds overhead |
| pa11y | Runs axe‑core under the hood; provides CLI and CI reporting | Easy to configure with URLs or local files | Same OCR limitations as axe‑core |
| Lighthouse (Chrome DevTools) | Accessibility audit includes WCAG 1.4.5 checks | Built‑in to Chrome; provides scores and suggestions | Less configurable for custom heuristics |
| Custom script using Tesseract OCR | Renders page to canvas, runs OCR, compares OCR output to visible text nodes | Can catch images of text that lack any ARIA label | Requires headless browser + OCR engine; slower; false positives on complex backgrounds |
Example: axe‑core configuration in a Jest test
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('page passes WCAG 1.4.5', async () => {
await page.goto('https://example.com');
const results = await axe(page);
expect(results).toHaveNoViolations();
});
Mobile‑Focused Automated Checks
| Tool / Library | Primary Mechanism | Strengths | Limitations |
|---|---|---|---|
| Accessibility Scanner (Android) | Scans view hierarchy for missing contentDescription and low‑contrast text | Quick UI feedback; integrates with Gradle | No OCR; cannot detect images of text that have a description but still pixelated |
| Xcode Accessibility Inspector (iOS) | Highlights UI elements lacking labels or traits | Real‑time feedback during development | Manual interaction required; no batch mode |
| Espresso + Accessibility Test Framework (Android) | Asserts that each view with text-like content has a non‑empty contentDescription | Can be part of instrumented test suite | Requires explicit identification of image views |
| UI Test + OCR (iOS/Android) | Captures screenshot, runs OCR, compares to accessibility labels | Can detect hidden text in images | Adds test execution time; OCR accuracy varies with font/style |
| Google’s ML Kit (Android/iOS) | On‑device text detection; can be used in test scripts to verify that any detected text has a corresponding accessibility label | Works offline; fast on modern devices | Requires adding ML Kit dependency to test app; still needs custom assertion logic |
Example: Espresso test that flags ImageView with missing description
@Test
public void imageViewHasContentDescription() {
onView(withId(R.id.submit_button))
.check(matches(isDisplayed()))
.check(matches(withContentDescription(not(isEmptyOrNullString()))));
}
If the contentDescription is empty, the test fails, indicating a potential WCAG 1.4.5 violation.
Combining Automated and Manual Steps
Automated tools excel at catching missing labels or descriptions. They are less reliable at judging whether an image of text is *essential*. Therefore, a typical workflow:
- Run automated scans to collect candidates (images with missing/poor alt/text).
- Review each candidate manually to decide essentiality.
- For non‑essential images, replace with real text or provide appropriate label.
Fixing Violations: Code Examples and Best Practices
Web: Replace Image of Text with Real Text
Before
<h1><img src="welcome-banner.png" alt="Welcome to Our Store"></h1>
After
<h1>Welcome to Our Store</h1>
If the visual design requires a specific font or effect, use CSS:
<h1 class="welcome-heading">Welcome to Our Store</h1>
.welcome-heading {
font-family: 'Pacifico', cursive;
font-size: 2.5rem;
letter-spacing: 0.05em;
background: linear-gradient(to right, #ff7e5f, #feb47b);
-webkit-background-clip: text;
color: transparent;
}
Web: Provide Meaningful Alt Text When Image Is Essential
<figure>
<img src="sales-chart.png" alt="Bar chart showing Q1 2024 sales: January $120k, February $135k, March $150k">
<figcaption>Q1 2024 sales performance</figcaption>
</figure>
Android: Use TextView Instead of ImageView for Labels
Before
<ImageView
android:id="@+id/sign_in_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/sign_in_text"
android:contentDescription="" />
After
<TextView
android:id="@+id/sign_in_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sign In"
android:textAppearance="?attr/textAppearanceHeadline6"
android:contentDescription="@string/sign_in" />
If a custom font is required, apply it via android:fontFamily or a custom Typeface.
Android: Supply Proper contentDescription
<ImageView
android:id="@+id/logo"
android:layout_width="96dp"
android:layout_height="96dp"
android:src="@drawable/app_logo"
android:contentDescription="@string/app_name" />
iOS: Replace UIImageView with UILabel
Before
let logoImage = UIImage(named: "AppName")
let logoView = UIImageView(image: logoImage)
logoView.accessibilityLabel = "" // missing
After
let logoLabel = UILabel()
logoLabel.text = "MyApp"
logoLabel.font = UIFont(name: "Pacifico-Regular", size: 28)
logoLabel.accessibilityLabel = "MyApp"
iOS: Provide accessibilityLabel for Essential Images
let chartImage = UIImage(named: "QuarterlyChart")
let chartView = UIImageView(image: chartImage)
chartView.accessibilityLabel = "Bar chart showing revenue: Q1 $1.2M, Q2 $1.3M, Q3 $1.5M, Q4 $1.8M"
Custom Drawing Views: Expose Text via Accessibility
Android (Canvas)
@Override
protected void onDraw(Canvas canvas) {
canvas.drawText("Sign In", 10, 30, paint);
}
// Accessibility node provider
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setText("Sign In");
}
iOS (CoreGraphics)
override func draw(_ rect: CGRect) {
let text = "Sign In"
text.draw(at: CGPoint(x: 10, y: 30), withAttributes: [.font: UIFont.systemFont(ofSize: 16)])
}
// Accessibility
override var accessibilityLabel: String? {
get { return "Sign In" }
set { /* optional */ }
}
General Best Practices
- Prefer native text components (
<h1>‑<h6>,<p>,<button>,<label>,TextView,UILabel) over images for any readable content. - When a custom font is essential, load it via
@font-face(web) or bundle the.ttf/.otffile and apply it to the text component (mobile). - Use CSS properties like
background-clip: textormix-blend-modeto achieve visual effects without sacrificing text semantics. - For icons that also convey a word (e.g., a house icon meaning “Home”), provide an
aria-labelorcontentDescriptionthat matches the visible word, and consider adding visible text alongside the icon for clarity. - Test with actual user settings: system font scaling, high contrast mode, and screen readers to confirm that the replacement behaves as expected.
Autonomous, Persona‑Driven Exploration Checks This Criterion
SUSA’s autonomous QA platform can be pointed at an APK or a web URL and will exercise the application using a variety of simulated user personas. Each persona embodies distinct interaction patterns, which helps surface WCAG 1.4.5 issues that might remain hidden under scripted testing.
How the Curious Persona Finds Images of Text
The curious persona explores every tappable element, lingers on screens, and attempts to read any visible label. When it encounters an ImageView or <img> that contains readable characters but lacks an accessible label, the persona logs a “missing text alternative” finding. Because the persona spends extra time inspecting unfamiliar controls, it is more likely to notice a stylized button that looks like text but is actually an image.
How the Impatient Persona Triggers Edge Cases
The impatient persona performs rapid taps and swipes, often bypassing introductory animations. If an app relies on a splash‑screen image that contains the version number, the impatient persona may skip the splash and never see the version, but the platform still records the image as a candidate for WCAG 1.4.5 evaluation. The platform then checks whether that image has an appropriate description; if not, it flags a violation regardless of whether the persona saw it.
How the Accessibility Persona Validates Fixes
The accessibility persona simulates a user who relies on TalkBack or VoiceOver. It navigates using swipe gestures, listens to spoken feedback, and attempts to copy or share any text it hears. When the persona encounters an image of text that is spoken only as “image” or a vague description, it records a failure. Conversely, when the image is replaced with real text or a proper label, the persona confirms that the spoken output matches the visible characters and that the text can be selected and copied.
Cross‑Session Learning and Regression
After the first run, SUSA remembers which screens contained images of text and whether they passed or failed. On subsequent runs, the platform prioritizes those screens for deeper checks (e.g., verifying contrast after a font change, ensuring that a newly added label still matches the visual text). This learning loop reduces the chance of regressions where a developer replaces an image with real text but forgets to update the label.
Generating Regression Scripts
When SUSA confirms a fix, it can auto‑generate an Appium test (Android) or a Playwright test (web) that asserts the presence of the expected text or label. For example, after detecting that a button’s image was replaced with a TextView containing “Submit”, the generated script might look like:
// Appium Java
@Test
public void submitButtonHasCorrectText() {
MobileElement button = driver.findElement(By.id("submit_button"));
Assert.assertEquals(button.getText(), "Submit");
}
// Playwright
test('submit button shows correct text', async ({ page }) => {
const button = page.locator('#submit_button');
await expect(button).toHaveText('Submit');
});
These scripts become part of the regression suite, guaranteeing that future changes do not re‑introduce the image‑of‑text anti‑pattern.
Edge Cases That Only Show Up in Production
Dynamic Font Loading
Some web apps load custom fonts via JavaScript after the initial paint. If the font fails to load (network issue, CORS block), the browser may fall back to a system font, causing a layout shift that reveals an underlying image of text that was hidden by the font‑based styling. Automated scans run in a controlled environment might miss this because the font loads correctly in the test lab.
Remote‑Config A/B Tests
Feature flags can serve different UI variants to different user segments. A variant that uses an image‑based banner for a promotional campaign may never be exercised in the staging environment if the flag is off. In production, a subset of users sees the image, and if the alt text is missing, they encounter a WCAG violation that remains invisible to internal QA.
Canvas‑Based Charts with Export Buttons
A chart rendered inside a <canvas> element may have an accompanying “Download PNG” button. The button’s label is often an icon only, but the chart itself contains valuable numeric data as part of the pixel image. If the chart is not also provided as a data table or accessible SVG, users who cannot perceive the image lose the data. This problem is frequently discovered only when a user attempts to extract data via a screen reader in a live session.
Localized Assets
Apps sometimes swap out image‑based text for different languages. If the localization process omits updating the contentDescription or alt text, the wrong language description may be attached, leading to a mismatch between spoken output and visible characters. This mismatch can slip through manual checks if the tester only verifies the default language.
System‑Level Font Scaling
On Android, users can enable “Font size” or “Display size” in Settings. Some custom views that draw text via Canvas do not respect the scaling factor, causing the drawn text to become too small or too large relative to the container. While the text remains programmatically available, the visual presentation may become illegible, which is a related usability concern that often appears only after a user changes the setting in the wild.
Gesture Overlays
Certain apps place a semi‑transparent overlay that captures gestures (e.g., a tutorial overlay). If the overlay contains an image of text explaining the gesture, and the overlay is not exposed to the accessibility hierarchy, screen readers will skip it entirely. The issue may not surface in automated tests that ignore overlays, but a real user trying to learn the gesture will miss the instructions.
Short Checklist for Developers and QA
| ✅ Item | Description | How to Verify |
|---|---|---|
| 1 | No image contains readable characters unless essential. | Visually inspect; if you can read the text, check for a text‑based alternative. |
| 2 | Essential images have appropriate alt (web) or contentDescription/accessibilityLabel (mobile). | Use DevTools, Accessibility Scanner, or VoiceOver/TalkBack to confirm the description matches the visible text. |
| 3 | Text is available as real text in the DOM or native view hierarchy. | Inspect element hierarchy; ensure the string appears as a node, not only as an image attribute. |
| 4 | Text scales with user‑controlled font size or zoom. | Increase system font size (mobile) or page zoom (web) to 200 %; verify no clipping or pixelation. |
| 5 | Custom‑drawn text exposes itself via accessibility APIs. | Run accessibilityNodeInfo (Android) or accessibilityLabel (iOS) returns the drawn string. |
| 6 | Icons that imply a word have a matching label. | Activate screen reader; confirm spoken label matches the icon’s meaning. |
| 7 | Localized versions of image‑based text have updated descriptions. | Switch language/locale; verify description updates accordingly. |
| 8 | Regression tests assert presence of expected text/label. | Run generated Appium/Playwright scripts; they should pass after each change. |
Run this checklist as part of each UI change set or during a dedicated accessibility sprint.
Closing Takeaways
WCAG 1.4.5 is not a decorative rule; it directly affects anyone who needs to read, resize, or customize text on screen. The most common pitfalls are straightforward: using images where a plain text element would serve the same purpose, and forgetting to supply a meaningful alternative when the image truly is required.
By treating text as a first‑class citizen—choosing native text components, styling with CSS or platform‑specific typography attributes, and exposing any custom drawn text through accessibility APIs—you eliminate the bulk of violations. Automated tools (axe‑core, Accessibility Scanner, Espresso/UI Test OCR catches) are excellent for catching missing labels, but human judgment remains essential to decide whether an image of text is truly essential.
When you integrate an autonomous, persona‑driven explorer like SUSA into your workflow, you gain a safety net that exercises the app through varied interaction patterns, surfaces hidden image‑of‑text issues, and can even generate regression scripts to keep the code base honest over time.
Finally, remember that accessibility is not a one‑time checklist item but a continuous practice. Pair automated scans with manual persona‑driven tests, keep the regression suite up to date, and treat every piece of visible text as a candidate for real, accessible text. The result is a product that works better for everyone—users with disabilities, power users, and the casual visitor alike.
---
*This guide is intended for engineers who need to apply WCAG 1.4.5 in real‑world projects. Keep it bookmarked, refer to the tables and code snippets when you encounter image‑based text, and let the testing practices described here become part of your definition of done.*
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts. New to the category? Start with what autonomous product intelligence & QA means.
Try SUSA Free