
Roles, permissions and user states in the UI: what to show when something is not possible
When a role or permission blocks an action, the UI can hide, disable or explain. Criteria from Nielsen, SAP and OWASP, with accessibility and security in mind.
Hide or disable?
Jakob Nielsen recommends keeping unavailable commands visible as disabled controls, and hiding only as the exception what the user can never use, for example because of their subscription level or permissions. His argument is that hiding hurts discoverability: someone who cannot find a feature may conclude it does not exist, and a hidden control gives no hint of what would unlock it. As an example he cites Microsoft Office’s adaptive menus: people searched for the functions that “disappeared”, and later versions went back to showing all items, with the unavailable ones disabled.
The practical question is: will this person ever be able to use the feature? If they could with another permission, another plan or later, show it disabled. If they never can, hiding it is reasonable.
SAP’s design system for Engagement Cloud follows similar logic: if the element is part of the main flow, it should appear disabled. Hiding is recommended only for secondary functions that only certain users need. The difference matters because a hidden control teaches nothing, while a visible disabled one shows that the feature exists and when it might be available.
How do you explain the reason?
Nielsen is direct: you must clearly explain why the feature is unavailable. A disabled control without an explanation becomes a communication dead end. It can be done with a tooltip or a visible link that says what is needed to get access.
SAP proposes a concrete pattern: when hovering over the disabled element, a tooltip appears explaining why the function is not accessible. And when a whole screen is outside the user’s reach, the screen shows a “Permission Denied” empty state with a button to go back. That avoids sending the person to a place they cannot use without context. According to SAP, the description of that screen names the required permission; if that detail is sensitive, use the generic message from the next section instead. For the general design of these screens, see empty states.
The explanation should be concrete. “Permission Denied: Segments / Create” says what is missing. “You don’t have access” forces guessing.
What about security?
Explaining the reason can leak information. If a user without permission reads “This feature requires the billing administrator role”, they now know a role exists that they do not have. If the information is sensitive, SAP recommends showing only a generic message such as “Access denied” or “Not authorized”, without revealing technical reasons.
The practical rule is to distinguish two cases. For non-sensitive functions, the explanation is useful and helps the person ask for access. For sensitive data or unauthorized actions, the message should be generic. That decision is taken by whoever understands the risk, not by whoever designs the button.
Does hiding a control protect it?
No. What the interface shows is user experience, not security. OWASP’s authorization guidance says access control checks must be enforced on the server (or at a gateway, or in serverless functions), that permissions must be validated on every request and that the application should deny access by default. According to that guide, client-side checks can improve the experience but should never be the decisive factor in granting or denying access.
In practice, design the “no permission” state to be clear and ask engineering to make the server reject the action even if someone reaches it through a direct URL or the API.
And accessibility?
A disabled control must remain readable and understandable for people using assistive technology. Nielsen warns that a disabled state that is too faint often has low contrast, which affects people with low vision, and proposes a muted colour with greater contrast. For contrast criteria for components, see accessible visual identity.
Smashing Magazine points to a common technical problem: disabled buttons often cannot be focused with the keyboard. It recommends using aria-disabled rather than relying only on the disabled attribute, and warns that pointer-events: none in CSS does not prevent focus or keyboard navigation. That way the person can reach the control, hear its state and read the explanation.
Smashing also suggests ARIA live regions to announce state changes. If a button becomes enabled after a form is completed, someone using a screen reader should find out.
What alternatives exist to a disabled button?
Vitaly Friedman warns that a disabled button signals that something is wrong but almost never says what, and that it often cannot be focused with the keyboard. Although his article is mostly about forms, his alternatives also work for permissions:
- Leave the button active and explain the error when the action is attempted, with an error summary that guides the person.
- Replace the unavailable option with informative content, such as a “notify me when available” prompt.
- Show a hint or tooltip that explains the reason.
- Offer a way out, such as a link to support or to whoever manages permissions.
- Reserve the disabled state for narrow cases, such as preventing a double submission.
For permissions, the rule from Nielsen and SAP still stands: if you disable, you explain.
Example
Illustrative example: a billing platform has three roles: read-only, editor and administrator. A read-only user sees the “Void invoice” button disabled, with a tooltip that says: “Only administrators can void invoices. Ask your administrator for access.” That message lets them understand the limit and know whom to ask. The billing settings screen, which is not theirs, does not appear in their menu. If they try to reach it through a direct URL, they see a “Permission Denied” state with a button to go back, not the content; the server also rejects the request even if the interface fails. Customer data never appears, because the user has no permission to see it.
For your next screen: list the product’s roles and, for each control, note whether the role that cannot use it will ever need it. That list decides between hiding and disabling.
When should you NOT disable?
- When the disabled button creates confusion because the person cannot understand why it does not respond and there is no way to find out.
- When the action is sensitive and showing it, even disabled, reveals information.
- When the deactivation is temporary because a process is underway. A loading state usually communicates better, as the note on loading states explains.
- When the action can be attempted and the system can give a useful message when it fails. Then leave it active and explain the error.
Common mistakes
- Disabling without explanation, leaving a control that does not respond.
- Hiding functions the user will need, which stops them from discovering how to get access.
- Showing the technical reason for sensitive data.
- Relying on hiding the control as the only barrier: validation must also happen on the server.
- Using
pointer-events: noneto simulate a disabled button. - Redirecting to a screen the user has no permission for without context.
- Not announcing state changes to people using screen readers.
Related in the wiki
To decide what to show in error cases, read error state patterns; this note limits itself to errors caused by role and permission. For the complexity that roles often bring, Tesler’s law explains why it is not always wise to hide decisions. And for how screen readers interpret a control’s state, the accessibility tree is the reference.