Availability states
Users need a way to understand why a feature isn’t available and what to do to enable it.
Why this matters
Features within ClickHouse products have various states of availability. They can be blocked by billing tiers, organization configuration, service settings and deployment models. When incorrectly handled, the interface can end up in one of these confusing states:
- An element is there, but does nothing or causes an error
- An element they’re looking for isn’t there at all
Choosing to hide features based on their availability runs the risk that users can’t discover what the product is capable of doing. But simply disabling every button with an explanation makes the app look broken. ClickHouse products err on the side of showing things over hiding them. But if a clear explanation of how something can be enabled isn’t possible, then hiding the feature is the next best thing.
This document attempts to provide guidance on when to show or hide features based on their availability.
Decision sequence
The following list provides a way to decide how to handle features that are unavailable. First match wins. It tells you why something is unavailable and how to communicate that to the user. Where the restriction appears tells you where to put that explanation.
1. Does this feature exist in the current deployment model? If not, hide it. No explanation. See Absent is not disabled.
2. Is it based on the status of something outside of the user’s control? Block whatever action is dependent on the status.
For example: If a user is unable to create a table while a service is starting up, show them the form to create the table but disable the button that actually submits the form. If there’s room, include an explanation next to the button that communicates why it’s disabled. If there’s not room, use a tooltip on the button to communicate the reason.
In some cases, it’s better to prevent the user from seeing the form/action/activity at all until the status blocking the action is resolved. If this is the case, do not allow the user to navigate to the screen by disabling the link/button leading to it or adding a dialog between the click and the page.
3. Is it based on the status of an action the user triggered? Disable it while in flight, with no tooltip.
For example: If a user submits a form and we’re waiting for a response before navigating to the post-submit state. Add a progress indicator of some sort, like Button’s loading prop. This is one case where the lack of a text explanation is fine, since the progress indicator is the explanation and the state resolves on its own.
4. Is it a value rather than an action? Use a read-only UI element, not a disabled input. Read-only content stays selectable, copyable, and readable by assistive technology.
5. Otherwise — who must take the next step to enable the feature?
| Who acts | Treatment |
|---|---|
| The viewer, via a purchase they’re authorized to make | Visible and locked, with an upgrade CTA |
| The viewer, by changing a setting they’re authorized to change | Visible, with a link to that setting |
| The viewer, but only outside the platform | Visible, disabled, with instructions and/or link to docs |
| Someone else must act | Visible with “who to ask” in helper text or tooltip |
| Nobody | Hide it |
Wherever possible, allow users to activate the feature without leaving the platform (rows 1 and 2). A disabled control with a reason will tell people what’s wrong but it must also lead them to the resolution. A link to detailed docs is the fallback for when there’s no setting to link to — for example, when the resolution lives outside the platform.
A permanently unreachable control sitting in a nav list is clutter, not education. Show it where it’s central to the task at hand. Otherwise, hide it.
Communication principles
Absent is not disabled
A feature that doesn’t exist in the current deployment model should never appear as restricted, disabled or unavailable — it’s absent. For example, BYOC exposes infrastructure controls that have no meaning in Cloud, and Cloud has features with no BYOC equivalent. Hide these unconditionally — no tooltip, no helper text, no upsell.
Deployment model is the clearest example, but the principle is general: if nothing anyone does inside the product will ever make a control work, it shouldn’t be on screen implying otherwise.
This is not the same as a missing entitlement:
- Entitlement is a purchase or a configuration setting. There’s a real path from here to there, so showing the locked feature is useful.
- Deployment model is architecture. There is no “upgrade to BYOC” button, so a locked control implies a self-serve path that doesn’t exist.
The test: if someone asked support “how do I turn this on,” would the answer be a purchase, a role change, or “you can’t”? The third answer means hide it.
✅ Do: Omit BYOC infrastructure settings entirely in Cloud organizations.
❌ Don’t: Show BYOC settings greyed out with “Available for BYOC organizations.”
Empty is not blocked
These share a visual family and get conflated, but they carry different obligations.
Empty means the container is legitimately empty and the user can fill it. Nothing created yet, or no results for the current filter. It needs a path to populate it.
Blocked means the user can’t use this at all. Permission, entitlement, or an unmet prerequisite. It needs the reason and who can unblock it.
“No results” and “you don’t have access” are different messages. Someone who gets the first when they should have gotten the second will go looking for data that was never going to appear.
✅ Do: You don’t have access to backups. Ask an organization admin for the Admin role.
❌ Don’t: No backups found.
Where the restriction appears
Tooltips fit nav items and buttons. They work less well elsewhere — a tooltip on a form field competes with the field’s own help text, and a tooltip can’t carry the explanation for a whole page.
| Surface | Treatment | Mechanism |
|---|---|---|
| Nav item | Hide, or disable with reason | aria-disabled + Tooltip |
| Button or menu item | Disable with reason, or lock with CTA | aria-disabled + Tooltip/Popover, or helper text + Link |
| Form field | Read-only, or disable with reason | readonly; helper text, not tooltip |
| Form submit | Disabled if status is temporary | See 2 in Decision sequence |
| Section within a page | Inline blocked state | Alert or Panel with CTA |
| Whole page or route | Full-page blocked state | Empty-state layout with CTA |
Never hide a page someone has already navigated to. Render a full-page blocked state naming the reason and the next step. A 404 claims the page doesn’t exist, which is a different thing to say — see Empty is not blocked.
Copy patterns
Say what’s unavailable and what changes it. Never just “unavailable.”
| Reason | Format | Example |
|---|---|---|
| Prerequisite | [Condition] to [action] | Service must be running to create backups. |
| Permission | [Role] role required. [Who to ask] | Admin role required. Contact your organization admin. |
| Entitlement | Available on [plan] plan | Available on Enterprise plan. |
| Technical | Unavailable due to [constraint] | Unavailable due to a network error. |
Keep it to one sentence, if possible. If the reason genuinely needs more, the surface is wrong — use an Alert or an inline blocked state instead of a tooltip.
Common mistakes
❌ A tooltip that explains the feature instead of the restriction
A disabled toggle with an info icon describing what the feature does, but nothing about why the toggle is unavailable. The user learns what they’re missing and still doesn’t know how to get it.
✅ Instead: put the reason on the disabled control itself. If you need space for a feature explanation, consider a Popover instead of a Tooltip. Otherwise, drop it.
❌ The same restriction treated two ways in one flow
One tier-gated section disappears entirely while another one on the same screen renders disabled. There’s no consistent rule for users to learn.
✅ Instead: pick the treatment from the reason, not from the component that’s easiest to reach for.
❌ Upselling to someone who can’t buy
Showing “Upgrade to Enterprise” copy to a developer with no billing permission. They can’t act on it, so it’s just noise.
✅ Instead: show the upgrade path to people who can take it. For everyone else, name who they can ask (i.e. “Feature X is available on Enterprise plans. Ask an admin to enable it”).
❌ A blocked state dressed as an empty state
“No API keys found” when the real reason is that the viewer lacks permission to list them. The user goes looking for a bug that doesn’t exist.
✅ Instead: name the access problem and who can fix it.
Related
- Writing for product — copy for empty states, tooltips, and errors
- Tooltip — component API
- Buttons — component API
- Accessibility — broader accessibility guidance