Making Mendix Apps Accessible: Real Challenges, Real Fixes

Making Mendix Apps Accessible: Real Challenges, Real Fixes

A practitioner’s guide to solving WCAG 2.1 AA compliance gaps in Mendix Studio Pro 10.24+ using the Accessibility Helper and HTML Element widgets


You’ve just finished a sprint. The Mendix app looks clean, the nanoflows fire correctly, and the client is happy. Then the accessibility audit lands in your inbox.

That was my experience working on a production Mendix 10.24+ application. A BrowserStack accessibility scan surfaced a list of WCAG 2.1 AA violations I had not planned for, form inputs without labels, color contrast failures, icon-only buttons that screen readers couldn’t understand, and combo boxes that narrated confusing or misleading audio to assistive technology users.

This post documents the specific challenges I encountered and how I resolved them using two Mendix Marketplace widgets: the Accessibility Helper and the HTML Element widget. If you are building or auditing a Mendix application for accessibility compliance, these patterns should save you significant time.

Why accessibility matters in low-code apps

Accessibility is not optional. It is a legal and ethical requirement. The European Accessibility Act (EAA), which came into effect in June 2025, requires many digital products and services to meet WCAG 2.1 Level AA. In the United States, Section 508 and the Americans with Disabilities Act impose similar obligations on public-sector and many private-sector applications.

Low-code platforms like Mendix accelerate development enormously, but that speed comes with a trade-off. Pre-built widgets abstract away HTML, which means the developer does not always control the underlying DOM structure or ARIA attributes. When a widget does not expose the right accessibility properties, a screen reader user, or any user who relies on keyboard navigation, high-contrast modes, or other assistive technology, hits a wall.

WCAG 2.1 organizes its requirements into four principles: Perceivable, Operable, Understandable, and Robust. The issues I encountered map across all four of them. Let’s go through each scenario in turn.

Scanning for issues: BrowserStack Accessibility

Before any fix, you need to know what is broken. I used BrowserStack’s automated accessibility scanner to audit the full application. The tool crawls each page, flags WCAG violations by success criterion, and surfaces the affected DOM selector alongside the HTML snippet, exactly what you need to target a fix with precision.

What made it particularly valuable was the breakdown by severity and the mapping to specific WCAG success criteria (SC). Issues like SC 1.3.1 (Info and Relationships), SC 1.4.3 (Contrast Minimum), SC 2.4.1 (Bypass Blocks), and SC 2.5.3 (Label in Name) came up repeatedly. With that map in hand, I could cross-reference each issue against the Mendix widget generating it.

Running the scanner iteratively, fix a batch, scan again, also helped confirm that the changes were working as intended in the rendered DOM, not just in Studio Pro.

The two widgets that solve most problems

Before diving into specific scenarios, it helps to understand the two tools at the center of every fix.

Accessibility Helper

The Accessibility Helper (available from the Mendix Marketplace) is a containment widget. You drag the target widget, a button, a text box, a combo box, inside it, then configure HTML attributes to inject onto specific child elements using CSS selectors.

It supports both static (Text) and dynamic (Expression) attribute values, and attributes can be conditionally applied. Importantly, it targets elements via standard CSS selectors, so you can reach deep into a widget’s rendered DOM without writing custom code. The only restrictions are that you cannot use it to override class, style, widgetid, or data-mendix-id, since those are managed by Mendix’s core engine.

HTML Element widget

The HTML Element widget lets you create a custom HTML element at any point in the page. You configure the tag name, attributes, and content, either as static HTML or as a dynamic expression, and optionally use it as a container for other widgets. For accessibility purposes, it is ideal for injecting elements that Mendix does not natively render, such as a visually hidden label or an ARIA live region.

Together, these two widgets cover the vast majority of accessibility gaps you will encounter in a standard Mendix application.

Scenario 1: Button widget with icon only, no visible caption

Design frequently calls for icon-only buttons, a search icon, a delete icon, an expand control. When you configure a Mendix Button widget without a caption and only an icon, the rendered button element has no accessible name. A screen reader announces nothing meaningful, or announces the icon file name, which is useless.

📋 WCAG Reference

SC 2.5.3, Label in Name: For interactive components with visible text or an icon, the accessible name must contain the visible label.

SC 4.1.2, Name, Role, Value: All interactive elements must have an accessible name, role, and state exposed to assistive technology.

⚠️ The Challenge

The Mendix Button widget renders as <button> in the DOM.

With no caption set, the button has no text content and no aria-label attribute.

Screen readers announce the button by its icon name or announce nothing, leaving keyboard and screen reader users unable to identify the control.

✅ The Fix

Wrap the Button widget inside an Accessibility Helper widget.

Set the target selector to: button

Add an HTML attribute: name = aria-label, value = ‘Search’ (or the appropriate descriptive label).

The Accessibility Helper injects aria-label onto the rendered <button> element at runtime.

If the label needs to reflect a dynamic value (e.g. ‘Delete item: ‘ + $currentObject/Name), use Source Type = Expression.

💻 Example Configuration

Target selector:  button
Attribute name:   aria-label
Attribute value:  'Open search panel'   (Text source)
Result in DOM:
<button aria-label="Open search panel" class="btn mx-button">
<span class='glyphicon glyphicon-search'></span>
</button>

This approach keeps the visual design intact, the caption remains hidden, while providing screen reader users with a meaningful, context-appropriate label.

Scenario 2: Combo box with custom content and no caption

The Mendix Combo Box widget is a flexible input component that allows custom content inside each option. In designs where the combo box uses icons or color-coded indicators as its entire option representation, with no visible text label above or beside the widget, two distinct accessibility problems emerge.

📋 WCAG Reference

SC 1.3.1, Info and Relationships: Programmatic structure must match visual presentation; form inputs require associated labels.

SC 2.5.3, Label in Name: The accessible name of interactive elements must include or match any visible text label.

SC 4.1.2, Name, Role, Value: Inputs must expose their name, role, and current value to assistive technology.

⚠️ The Challenge

Without a visible label above the combo box, there is no associated <label> element in the DOM.

The combo box announces itself generically (‘combo box’) with no context about what value it represents.

Custom content inside options (icons, styled spans) may be read verbatim by screen readers, producing confusing or redundant audio output, for example, reading out CSS class names or decorative icon descriptions.

✅ The Fix

For the missing label: Wrap the Combo Box inside an Accessibility Helper. Target selector: .form-group or the specific widget container. Add aria-label = ‘Status filter’ (or the appropriate context-aware label).

For noisy option audio: Use the Accessibility Helper to target the custom content elements inside each option with aria-hidden=’true’, silencing decorative elements from the screen reader narration.

Alternatively, add a visually hidden label using the HTML Element widget placed directly before the Combo Box: set the tag to ‘label’, add a ‘for’ attribute pointing to the combo box input ID, and use CSS class ‘sr-only’ (screen-reader only) to hide it visually.

Contrast: ensure the combo box text and its background color meet the WCAG 4.5:1 minimum contrast ratio for normal text (SC 1.4.3).

💻 Example Configuration

Option A, Accessibility Helper approach:
Target selector:  .mx-combobox input
Attribute name:   aria-label
Attribute value:  'Filter by status'
Option B, HTML Element label:
Tag: label
Attribute: for = 'p.YourPage.combobox1'
Content: 'Filter by status'
Add CSS class: sr-only  (visibility:hidden, position:absolute)

The contrast piece deserves special attention. During my audit I found several combo box text colour values that had been styled to a mid-range grey for aesthetic reasons, resulting in contrast ratios below the required 4.5:1. The fix was straightforward, adjusting the custom Atlas UI SCSS variable, but it is easy to overlook in the design phase.

Scenario 3: Text box used with custom content context

A common Mendix pattern is to use a Text Box widget for user input, but to surround it with custom content containers, icons to the left, action links to the right, and a styled wrapper, rather than using the native label property. This is common in search bars, inline edit forms, and address lookup fields.

📋 WCAG Reference

SC 1.3.1, Info and Relationships: The label must be programmatically associated with its input, not merely adjacent in the visual layout.

SC 2.5.3, Label in Name: The accessible name of the input must include any visible label text.

SC 1.3.2, Meaningful Sequence: Content must be readable in a logical order when linearized.

⚠️ The Challenge

The Mendix Text Box widget generates an <input> element. If the Label property in Studio Pro is left empty (because the design uses a visually custom label), the input has no programmatic label at all.

A search field rendered as <input placeholder=’Enter an address and click search’> relies solely on the placeholder attribute. The placeholder disappears when the user starts typing, there is no persistent label for the screen reader to read.

Placeholder text also fails SC 3.3.2 (Labels or Instructions) because it provides no ongoing context once the user begins entering data.

Search inputs placed outside a <search> or <form> landmark region also violate SC 2.4.1 (Bypass Blocks) and SC 1.3.1.

✅ The Fix

Use the HTML Element widget to inject a proper <label> element immediately before the Text Box, with its ‘for’ attribute matching the input’s ID.

If the label should be visually hidden (e.g. the design uses a placeholder for visual purposes), apply an sr-only CSS class to keep it accessible but invisible.

Wrap the search input and its surrounding container inside a <search> landmark element using the HTML Element widget: set tag = ‘search’, and place the Mendix widgets inside it.

Remove placeholder-only patterns, always pair them with a persistent label.

💻 Example Configuration

HTML Element widget, visible label:
Tag: label
Attribute: for = 'p.CalendarPage.SNIP_AddressSearch.textBox1'
Content: 'Enter an address'
HTML Element widget, search landmark:
Tag: search
Content type: Container for widgets
(Place the text box widget inside it)

Scenario 4: Custom React date picker widget with no label or title

Third-party or custom React widgets published to the Mendix Marketplace often prioritize visual design over WCAG compliance. A React-based calendar/date picker widget may render a fully functional and attractive UI while exposing nothing useful to assistive technology.

In my case, the custom date picker had no label, no title attribute, no ARIA role, and no keyboard focus management that screen readers could follow. The workaround I used was to place a standard Mendix Text Box above the date picker with the caption ‘Select date’. The intent was that the text box would act as a visual label. But visually adjacent text and a programmatic label are not the same thing.

📋 WCAG Reference

SC 1.3.1, Info and Relationships: A label must be programmatically linked to its control, not merely positioned near it.

SC 2.4.3, Focus Order: Focusable components must receive focus in an order that preserves meaning and operability.

SC 4.1.2, Name, Role, Value: Custom widgets must expose their accessible name, role, and state.

⚠️ The Challenge

The ‘Select date’ Text Box above the date picker widget is a Mendix input field, not a <label>. It provides no programmatic link to the custom date picker below it.

The date picker’s input element has no aria-label, no aria-labelledby, and no title.

Screen readers announce the date picker without context, and keyboard users may find the calendar grid completely unnavigable.

The calendar cells themselves (rendered as <div> elements with tabindex) may not expose their date values or selected state.

✅ The Fix

Replace the Mendix Text Box ‘label’ with an HTML Element widget configured as a proper <label>.

Set the HTML Element’s ‘for’ attribute to the ID of the date picker’s underlying input element (inspect the DOM to find it).

Wrap the date picker inside an Accessibility Helper, targeting its root input element, and inject: aria-label = ‘Select date’, role = ‘combobox’ (if it behaves as one), and aria-haspopup = ‘dialog’.

If the calendar grid opens as a popup, add aria-modal = ‘true’ and role = ‘dialog’ to the calendar container using the Accessibility Helper.

For day cells rendered as non-semantic elements, use the Accessibility Helper to add role = ‘gridcell’, aria-selected = ‘true/false’, and aria-label = the formatted date string (driven by an Expression value).

💻 Example Configuration

HTML Element, label:
Tag: label
Attribute: for = '[date-picker-input-id]'
Content: 'Select date'
Accessibility Helper on the date picker:
Target: input
aria-label = 'Select date'
aria-haspopup = 'dialog'
Accessibility Helper on the calendar popup:
Target: .calendar-popup  (or equivalent class)
role = 'dialog'
aria-modal = 'true'
aria-label = 'Date picker calendar'

Scenario 5: Colour contrast failures across the app

Running the BrowserStack scanner surfaced a pattern I had not anticipated: several text elements, day labels in a calendar widget, secondary text in list items, and placeholder text, were rendered in mid-grey tones that failed the WCAG 4.5:1 contrast ratio threshold for normal text, or 3:1 for large text.

These were not individual widget bugs. They were theme-level decisions made in the Atlas UI design system that shipped with the application. The class .text-dark-grey, used throughout the calendar widget, had a hex value that produced a contrast ratio of approximately 3.2:1 against its white background, below the SC 1.4.3 minimum.

📋 WCAG Reference

SC 1.4.3, Contrast (Minimum): Text and images of text must have a contrast ratio of at least 4.5:1 (3:1 for large text, WCAG AA).

SC 1.4.11, Non-text Contrast: UI components and graphical objects must meet a 3:1 contrast ratio against adjacent colors.

⚠️ The Challenge

Theme-level SCSS variables set text colors optimized for visual aesthetics rather than accessibility.

The color “.text-dark-grey” fell below 4.5:1 in multiple contexts.

Placeholder text in text boxes was styled at an even lower contrast, giving it the ‘ghost text’ appearance.

Calendar day labels used a span element with class .mx-name-** rendered in a color that failed against its background.

✅ The Fix

Audit all custom color values against the WCAG contrast thresholds using a contrast checker tool.

For global fixes, update the Atlas UI SCSS variables in your app’s custom theme folder.

For widget-specific overrides, use the HTML Element widget or a CSS snippet to override the specific selector.

For placeholder text, set the ::placeholder pseudo-element color to at least #767676 on a white background (contrast ratio 4.54:1).

Test with both light and dark system themes, and in high contrast mode, if your user base requires it.

Scenario 6: Visible text not matching accessible name (SC 2.5.3)

A more subtle issue appeared on calendar date header buttons. The widget rendered a visual label showing only ‘W’ and ‘1’ (week letter and day number), but the ARIA label on the same element said ‘0 events on Wednesday, July 1, 2026’. This is actually a case where the accessible name is more informative than the visible label, which is good for screen reader users, but it triggered an SC 2.5.3 violation because the visible text (‘W’) was not contained within the accessible name.

SC 2.5.3 requires that the accessible name of a control must include any visible text label it displays, so voice control users can activate it by speaking the visible text.

📋 WCAG Reference

SC 2.5.3, Label in Name: Where an interactive component uses visible text as its label, the accessible name must contain that visible text.

⚠️ The Challenge

Calendar date header: visible text ‘W’ (Wednesday abbreviation); aria-label = ‘0 events on Wednesday, July 1, 2026’.

The visible string ‘W’ does not appear within the aria-label string, so voice control users cannot activate the button by saying ‘W’.

The Accessibility Helper flags this as a violation of SC 2.5.3.

✅ The Fix

Update the aria-label value to include the visible text as a substring. For example: ‘W, 0 events on Wednesday, July 1, 2026’.

In the Accessibility Helper, use Source Type = Expression to build this value dynamically from the date object.

The expression might look like: ‘W, ‘ + toString($YourObject/EventCount) + ‘ events on ‘ + formatDateTime($YourObject/Date, ‘EEEE, MMMM d, yyyy’)

This ensures the accessible name contains the visible label, satisfying SC 2.5.3, while still providing the richer description for screen reader users.

Verifying your fixes

Fixing the Studio Pro configuration is only half the job. You need to confirm the attribute is present in the rendered DOM at runtime and behaves correctly with real assistive technology.

Automated scanning

Run BrowserStack (or axe DevTools as a browser extension) after each batch of fixes. These tools catch the majority of detectable WCAG violations quickly and show you the exact element and rule that is failing.

Screen reader testing

Test with at least two screen reader and browser pairings. The most common combinations for web applications are NVDA with Chrome on Windows, and VoiceOver with Safari on macOS. Tab through every form element and interactive widget. Listen for the accessible name, role, and state that gets announced, that is your ground truth.

Specific things to listen for: does the button announce ‘Open search panel, button’ rather than ‘button’? Does the combo box announce its label before its current value? Does the date picker announce ‘Select date, combo box’ when focused?

Keyboard-only navigation

Disconnect your mouse. Tab through the entire page. Verify that every interactive element receives visible focus, that the tab order follows a logical reading sequence, and that you can activate every control using the keyboard alone.

Contrast checking

Use a browser extension such as Color Contrast Analyzer or the built-in DevTools accessibility panel to verify contrast ratios on any element flagged in the audit. Pay particular attention to text rendered on colored backgrounds and to placeholder text.

Key takeaways

If I had to distil this experience into practical guidance for any team building on Mendix 10.24+, it would be this:

  • Scan early, scan often. Don’t treat accessibility as a post-development audit. Run BrowserStack or axe DevTools during development, not just before release. Fixing issues while the page is fresh is far faster than retrofitting.
  • Two widgets cover most cases. The Accessibility Helper and the HTML Element widget between them resolve the vast majority of WCAG gaps in standard Mendix widgets. Learn how to use CSS selectors confidently, that is the skill that makes the Accessibility Helper powerful.
  • Placeholder text is not a label. This is one of the most common mistakes in Mendix forms. Always pair placeholder text with a persistent, programmatically associated label.
  • Contrast is a theme-level concern. WCAG contrast failures often originate in design token decisions made early in the project. Audit your Atlas UI color variables against the 4.5:1 threshold before the first sprint, not after the last one.
  • Test with real assistive technology. Automated scanners catch perhaps 40-50% of WCAG issues. Manual testing with NVDA, VoiceOver, and keyboard-only navigation finds the rest. There is no substitute.
  • Custom widgets need custom fixes. React-based Marketplace widgets may not expose any accessibility attributes at all. Budget time to audit and remediate them individually, using the Accessibility Helper to inject ARIA attributes onto their rendered DOM elements.

Further reading

Mendix Docs, Introduction to Accessibility in Mendix

Mendix Docs, HTML Element widget

W3C, Web Content Accessibility Guidelines (WCAG) 2.2