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

By · June 13, 2026 · 14 min read · WCAG Guides

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:

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

Step 2: Determine If the Image Is Essential

Ask:

If the answer is *no* to essentiality, proceed to step 3.

Step 3: Verify Programmatic Text Exists

Web:

Android:

iOS:

Step 4: Check Scalability and Custom Styling

Step 5: Document Findings

Record each violation with:

Automated Testing Tools and Techniques

Web‑Focused Automated Checks

Tool / LibraryPrimary MechanismStrengthsLimitations
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‑friendlyMay flag decorative images incorrectly; OCR‑based detection adds overhead
pa11yRuns axe‑core under the hood; provides CLI and CI reportingEasy to configure with URLs or local filesSame OCR limitations as axe‑core
Lighthouse (Chrome DevTools)Accessibility audit includes WCAG 1.4.5 checksBuilt‑in to Chrome; provides scores and suggestionsLess configurable for custom heuristics
Custom script using Tesseract OCRRenders page to canvas, runs OCR, compares OCR output to visible text nodesCan catch images of text that lack any ARIA labelRequires 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 / LibraryPrimary MechanismStrengthsLimitations
Accessibility Scanner (Android)Scans view hierarchy for missing contentDescription and low‑contrast textQuick UI feedback; integrates with GradleNo OCR; cannot detect images of text that have a description but still pixelated
Xcode Accessibility Inspector (iOS)Highlights UI elements lacking labels or traitsReal‑time feedback during developmentManual interaction required; no batch mode
Espresso + Accessibility Test Framework (Android)Asserts that each view with text-like content has a non‑empty contentDescriptionCan be part of instrumented test suiteRequires explicit identification of image views
UI Test + OCR (iOS/Android)Captures screenshot, runs OCR, compares to accessibility labelsCan detect hidden text in imagesAdds 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 labelWorks offline; fast on modern devicesRequires 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:

  1. Run automated scans to collect candidates (images with missing/poor alt/text).
  2. Review each candidate manually to decide essentiality.
  3. 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

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

✅ ItemDescriptionHow to Verify
1No image contains readable characters unless essential.Visually inspect; if you can read the text, check for a text‑based alternative.
2Essential images have appropriate alt (web) or contentDescription/accessibilityLabel (mobile).Use DevTools, Accessibility Scanner, or VoiceOver/TalkBack to confirm the description matches the visible text.
3Text 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.
4Text scales with user‑controlled font size or zoom.Increase system font size (mobile) or page zoom (web) to 200 %; verify no clipping or pixelation.
5Custom‑drawn text exposes itself via accessibility APIs.Run accessibilityNodeInfo (Android) or accessibilityLabel (iOS) returns the drawn string.
6Icons that imply a word have a matching label.Activate screen reader; confirm spoken label matches the icon’s meaning.
7Localized versions of image‑based text have updated descriptions.Switch language/locale; verify description updates accordingly.
8Regression 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