Retrofitting Accessibility Into Existing PCF Controls
A technical guide for teams facing an accessibility audit across custom Dynamics 365 and Power Apps controls: triage by users and journeys, the fixes that matter first, shared against per-control work, test evidence and an honest conformance statement.
To retrofit accessibility into a portfolio of PCF controls built without it, triage before you fix. Rank each control by how many users depend on it and whether it sits on a journey they cannot avoid, then fix the blocking defects first: keyboard operation and focus order, accessible names and roles, announcements for content that changes, colour contrast, and correct behaviour inside the host form's own tab order. Fix shared defects once in a common component layer, and keep per-control work for custom interactions such as grids and drag and drop. Evidence the result with keyboard and screen reader test records, and publish a conformance statement that lists known gaps honestly instead of claiming full WCAG conformance.
Why do custom PCF controls fail accessibility audits when the rest of the app passes?
Because the platform's accessibility stops at the edge of your control. Microsoft designs and tests the model-driven and canvas app shells, the standard controls and the form layout against accessibility standards and publishes its own conformance reports for them. A PCF control replaces part of that surface with your own markup, event handling and styling, and nothing the platform does can add a label, a role or keyboard support that your code never rendered.
Controls built for a demo tend to fail in the same places: clickable divs that a keyboard cannot reach, icons without names, drag and drop as the only way to move something, colours hard coded for one theme, focus that disappears after a re-render, and status changes that are shown but never announced. None of that is visible to a sighted mouse user, which is why it survives until an auditor or a user of assistive technology finds it. Where a control is being built from scratch, the accessibility checks belong in the plan and test phases described on our PCF controls development service. This page is for controls that already exist.
Which controls should be fixed first?
The ones people cannot route around. Build an inventory of every custom control, where it is placed and who uses it, then score each one before anyone opens the code. A control on an optional dashboard used by a handful of analysts is a lower priority than a lookup replacement on the case form every agent has to complete, even if the dashboard has more defects.
| Factor | What to record | Why it changes the order |
|---|---|---|
| Users | Security roles and apps whose forms include the control, and roughly how many people hold them. | More users means more people blocked by the same defect. |
| Journey | Whether the control sits on a required step, such as saving a case, submitting an application or approving a record. | A defect on an unavoidable step blocks the task, not just the convenience. |
| Alternative route | Whether the same data can be entered or read another way, such as a default control on another form. | A real alternative lowers urgency; it does not remove the defect. |
| Audience | Internal staff, citizens or customers through Power Pages, or both. | Public facing surfaces usually carry legal and contractual obligations sooner. |
| Severity of defects | Blocking (cannot operate or perceive), serious (possible with difficulty), or minor. | Blocking defects on high use journeys come first, regardless of how many minor defects exist elsewhere. |
| Future of the control | Keep, replace with a standard control, or retire. | Some controls are cheaper to remove than to retrofit, which is a decision covered in our buy, build or customise guide. |
How do you fix keyboard operation and focus order in a PCF control?
Every action a mouse user can take must be possible from the keyboard, in an order that follows what the user sees. Start with a keyboard only walk through each control from the field before it to the field after it, and write down every point where focus disappears, jumps, gets stuck or cannot trigger an action.
- Use native elements. A button element or a Fluent UI button gets focus, Enter and Space for free; a div with a click handler gets none of them.
- Never use a positive tabindex. It pulls the control out of the form's natural order. Use tabindex 0 only on custom widgets that need to be focusable and minus one for elements focused by script.
- Use one tab stop for composite widgets. Grids, lists, calendars and toolbars should take a single tab stop and move inside with the arrow keys, using a roving tabindex, as the WAI-ARIA Authoring Practices describe for each pattern.
- Keep focus where the user is after updateView. A control that re-renders its whole tree on every update throws focus back to the page, so render in place and restore focus to the same logical item.
- Close popups with Escape and return focus to the element that opened them. Do not trap focus in anything that is not a modal dialog.
- Provide an alternative to drag and drop. Moving a card or a booking needs a keyboard path and a single pointer path, such as a move action or a menu, which WCAG 2.2 also asks for dragging movements.
- Show focus visibly. Do not remove outlines without replacing them with an indicator that has enough contrast against its background.
- Avoid keyboard shortcuts that the host or assistive technology already uses.
How do you give a control accessible names, roles and states?
A screen reader announces what an element is, what it is called and what state it is in. Custom controls usually get the first wrong and omit the other two. In a model-driven form the host renders the field label outside your control, so the input inside it does not automatically inherit that label. Set an accessible name on the interactive element yourself, from the column display name available through the bound property's metadata where that fits, or from a manifest property the maker can set, and confirm in the accessibility tree of the browser developer tools what is actually exposed.
Use the role that matches the behaviour: a real button, checkbox, combobox, grid or listbox, with the ARIA states that go with it, such as aria-expanded, aria-selected, aria-checked and aria-invalid. Reflect the host state too. When context.mode.isControlDisabled is true, the element should be disabled or marked as such, not merely greyed out. Icon only buttons need a name, decorative images need to be hidden from assistive technology, and error messages should be associated with the field they describe. Wrong ARIA is worse than none, so add roles only where the native element cannot do the job.
How should a control announce content that changes without a page reload?
Anything a sighted user notices changing, a screen reader user needs to hear about, but only when it matters. PCF controls change constantly: a dataset finishes loading, a search returns results, a save succeeds or fails, a validation message appears, a card moves column. Put one polite live region in the control, keep it in the DOM from the start, and write short status text into it for the changes that carry meaning, such as "12 results" or "Booking moved to Thursday". Use an assertive announcement only for errors that stop the user.
Do not announce every updateView call, every resize or every background refresh, or the control becomes unusable with a screen reader in a different way. Loading states need a name as well as a spinner, and a control that replaces its content should not move focus unless the user asked it to. Because updateView can fire many times for one change on a real form, as covered in our guide to PCF controls in production, tie announcements to the change in data rather than to the lifecycle call.
What should you check for colour contrast, themes and zoom?
WCAG AA asks for sufficient contrast for text and for the parts of interface components and focus indicators people need to see, and it does not allow colour to be the only way information is conveyed. Custom controls fail this most often through hard coded brand colours, pale placeholder text used as a label, status shown only as a red or green dot, and chart or calendar colours chosen for looks. Check the exact ratios the standard you are measured against requires, and check them in every theme your app uses.
- Take colours from the platform or Fluent UI theme tokens rather than hex values in the stylesheet, so the control follows the app theme. Virtual controls that use the platform's React and Fluent UI get this most easily.
- Test with Windows contrast themes and the forced colours media query. Backgrounds used to draw borders, focus rings or selection vanish when the user's colours override yours.
- Add a text label, icon or pattern to every status that is currently shown by colour alone.
- Zoom the browser to high levels and narrow the window. Content should reflow inside the form section without horizontal scrolling for ordinary text, and nothing should be clipped.
- Check pointer target sizes on touch screens in the mobile app, where controls are often hardest to operate.
How does a PCF control behave inside the host form tab order?
As one well mannered citizen of a form it does not own. Users tab through a model-driven form from field to field, and your control should take its place in that sequence without surprises: focus enters where the field sits, moves through the control in reading order or as a single stop for a composite widget, and leaves to the next field. It should not grab focus when the form loads, steal focus when data refreshes, or keep focus inside when the user tabs on.
Test the control on the real form, not only in the test harness, because the harness has no surrounding fields, header, tabs or business process flow. Check it in each section layout and form factor it is configured for, in a quick create form if it is used there, and in the mobile app. Canvas apps set the order differently and have their own properties for it, so test the canvas host separately, as described in our guide to PCF controls in canvas apps.
What can be fixed once for every control, and what needs per-control work?
A portfolio built by different developers usually repeats the same defects, which is good news: a common component layer fixes many of them once. Fluent UI components give a real head start, because they implement keyboard handling, focus management and ARIA patterns for standard widgets, but they do not make a control conformant by themselves. Labels, the composition of components into a custom widget, colours overridden in your own styles, announcements and behaviour on the host form are still yours.
This split is how senior consultants and full-stack developers at Solzet run a portfolio retrofit, directly or white-label for Microsoft partners, from Yerevan, Armenia, with 8+ years of Dynamics 365 Customer Engagement and Power Platform work: the shared layer first, written once as a common package that each control picks up in its next versioned release, then per-control fixes in priority order.
| Shared fix, done once | Per-control work |
|---|---|
| A focus indicator style and theme token mapping used by every control. | The keyboard interaction model of each custom widget, such as a scheduling grid or a board. |
| A helper that sets the accessible name from the column metadata or a manifest property. | Alternatives to drag and drop and other pointer gestures specific to that control. |
| One live region announcer with agreed message rules. | Which changes are worth announcing, and the wording of each message. |
| Replacing clickable divs with shared button, link and menu components. | Focus restoration after that control re-renders its own data. |
| Lint rules for accessibility in JSX and automated accessibility checks in unit tests and the build. | Screen reader testing of the real journey the control sits on. |
| A shared test checklist and evidence template. | Known issues and workarounds recorded for that control in the conformance statement. |
How do you evidence accessibility testing for an audit?
With records someone else can repeat. Automated tools such as axe based scanners are worth running on every build, but they only find part of the defects that matter, so an audit needs manual evidence as well. For each control and journey, record the test and its result against the success criteria in scope.
- A test matrix naming the control, its version, the form or app, the browser, the assistive technology and its version, and the date.
- Keyboard only results from the field before the control to the field after it, with each defect linked to a work item.
- Screen reader results with the combinations your users actually have, commonly NVDA and JAWS on Windows, Narrator with Edge, and VoiceOver where the mobile app is used.
- Contrast, forced colours and zoom checks with screenshots.
- Automated scan output from the build, kept with the release.
- Retest results after each fix, and after each Microsoft release wave for the highest priority controls.
- Where possible, sessions with users of assistive technology on the real journey, which find problems no checklist does.
What should the conformance statement say about custom controls?
Exactly what the evidence supports, and no more. A statement that the app is fully WCAG compliant because Dynamics 365 has a Microsoft conformance report, or because an automated scan came back clean, will not survive an informed auditor. Microsoft's reports cover Microsoft's product, not the custom controls you added to it. State which standard and level you assessed against, which controls and journeys were tested and how, where each control partially conforms with the known issues listed, any accessible alternative route available meanwhile, and the dates by which the remaining fixes are planned. An honest partial statement with a credible plan is what buyers and regulators can work with. We do not give legal advice on which obligations apply to you.
Is rebuilding off the platform ever the answer? Rarely for accessibility alone, since the defects are in custom code that would need the same care anywhere. We recommend the right solution - whether that's Microsoft Dynamics 365, Power Platform, or a custom-built CRM. Some businesses need the Microsoft ecosystem. Others need full control without licensing. We deliver both. Where an organisation already needs full control of its user interface for other reasons and Microsoft licensing does not fit, a custom-built CRM on React, Node.js, PostgreSQL or .NET is the alternative we build, with accessibility designed in from the first screen.
Should custom UI stay in Dynamics 365 and Power Apps or move to a platform you build and own?
Can afford licensing and want the Microsoft ecosystem
Dynamics 365
Microsoft 365, Teams and Outlook integration, a mature partner ecosystem, Copilot, and apps for sales, service and field operations that are configured rather than built.
Need full control and zero licensing
Custom CRM
A CRM built on React, Node.js, PostgreSQL or .NET that you own outright: your data model, your hosting, no per-user subscription, and features shaped exactly to your process.
Not sure which fits
We help you decide
A short discovery weighs licensing budget, process complexity, integrations and long-term ownership, then recommends one path. We deliver both, so the recommendation has no reason to lean.
What do people ask us?
Are PCF controls accessible by default?
No. The model-driven and canvas app shells are designed and tested by Microsoft, but a PCF control renders its own markup, so its keyboard support, accessible names, announcements and contrast depend entirely on how it was built. Using Fluent UI components helps, because they implement keyboard and ARIA patterns for standard widgets, but labels, composition, custom styling and behaviour on the host form still have to be done and tested for each control.
Does using Fluent UI make a PCF control WCAG conformant?
It gives a head start, not conformance. Fluent UI components handle focus, keyboard interaction and ARIA roles for standard widgets such as buttons, menus and dialogs. A control can still fail through missing labels, custom widgets assembled from several components, colours overridden in its own styles, status changes that are never announced, drag and drop without an alternative, or focus lost after a re-render. Test the finished control, not the library.
Does Microsoft conformance documentation for Dynamics 365 cover our custom controls?
No. Microsoft publishes accessibility conformance reports for its own products, and they describe Microsoft's components. Custom PCF controls, web resources and scripts you add are your responsibility, and your conformance statement has to assess them separately with your own test evidence. Check the current Microsoft reports for what they cover and which versions they apply to.
How do you prioritise an accessibility retrofit across many PCF controls?
Inventory every control with the forms, apps and roles that use it, then rank by the number of users and whether the control sits on a step they cannot avoid, such as saving a case or submitting an application. Fix blocking defects on the highest use journeys first. Then look for defects repeated across controls and fix those once in shared components, and decide whether low value controls should be replaced with standard controls instead of retrofitted.
How do you test a PCF control with a screen reader?
On the real form, not only the test harness. Tab from the field before the control to the field after it and confirm each interactive element announces a sensible name, role and state, that changes such as loaded results and errors are announced, and that every action works from the keyboard. Repeat with the screen reader and browser combinations your users have, commonly NVDA or JAWS on Windows and VoiceOver on mobile, and record versions and results.
Can automated accessibility scanners prove a PCF control is compliant?
No. Automated checks such as axe based scanners are worth running on every build, because they catch missing names, invalid ARIA and some contrast failures cheaply. They cannot judge whether focus order makes sense, whether announcements are useful, whether a keyboard user can complete the task, or whether a drag operation has an alternative. An audit needs manual keyboard and screen reader testing recorded alongside the automated output.
What can we do for users while an inaccessible control is being fixed?
Offer an accessible route and say so in your conformance statement. In a model-driven app one option is a second form that uses standard controls for the same columns, assigned to the users who need it through security roles, or a documented way to complete the task elsewhere. Treat it as a temporary measure with a dated fix plan, not as a substitute for making the control accessible.
Where should you go next?
PCF controls development
Planning, coding, testing and deploying PCF controls in TypeScript and React, including the accessibility checks in the test plan.
PCF controls in production
Why controls behave differently on a real form: updateView timing, sizing, security, bundles and error handling.
PCF controls: buy, build, or customize?
Whether a control should be kept, replaced or retired, with the routes costed side by side.
PCF controls in canvas apps
How code components run in canvas apps and what changes compared with a model-driven form.
Hire PCF developers: team or freelancer
Who should own a portfolio of controls after the retrofit, and what the handover must include.
Custom CRM Development
CRM on React, Node.js, PostgreSQL and .NET for organizations that need full control without Microsoft licensing.
Which solution is right for your business?
Tell us what you need. A senior consultant replies within one business day with a recommendation - Dynamics 365, Power Platform, or a custom-built CRM - not a sales script.