Permissions & read-only
Every ERP screen renders differently for a clerk, an approver and an auditor. The framework ships no permission system — that is your server's job — but it has a clear stance on how a screen should degrade, and getting it wrong is how apps leak both information and support tickets.
The three degradations, and when to use each
| Treatment | Use when | Never use when |
|---|---|---|
| Omit — the control is not in the DOM | The user can never perform this action in this context (not an approver, wrong role, wrong record state). A control you can never use teaches nothing and invites a ticket. | The action is temporarily unavailable — omitting makes the UI look broken or inconsistent between visits. |
Disable — present, disabled | The action is possible for this user but blocked right now by state (nothing selected, form invalid, save in flight). Always pair with the reason. | The block is permanent for this user — that is what omit is for. A permanently disabled button is a permanent question. |
| Read-only — the value, not the input | The user may see the data but not change it. Render key-value facts, not disabled inputs. | Never as <input disabled> for display: disabled controls are skipped by keyboard, often fail contrast, and cannot be copied — while claiming to be a form field. |
<!-- A viewer sees FACTS, not disabled inputs. Same data, honest control. -->
<dl class="bo-kv">
<div><dt>Vendor</dt><dd>Umbrella Logistics</dd></div>
<div><dt>Amount</dt><dd class="bo-u-tabular">$12,400.00</dd></div>
</dl>
<!-- An editor sees the form -->
<div class="bo-form-field">
<label class="bo-form-field__label" for="vendor">Vendor</label>
<input class="bo-input" id="vendor" value="Umbrella Logistics" />
</div> The rule of thumb: the shape of the screen tells the truth about what you can do. A viewer's record detail is a document; an editor's is a form.
What the framework gives you
- Key-value facts — the read-only counterpart of a form section.
readonlyon.bo-input— for the narrow case where the field must stay a field (it submits, its text is selectable) but cannot be edited.--readonly/--disabledon rich text — with the two-channel rule: the modifier is the visible half,aria-readonly/aria-disabledthe programmatic half.- Empty and error states — including the "you don't have access" screen, which is a state, not an error.
Rules that keep permission bugs out
- The server decides; the UI reflects. Never rely on a hidden control as a security boundary — every action needs its own server-side check. A missing button is a courtesy, not a lock.
- Don't render what you then hide. Sending a disabled control that contains data the user may not see leaks it in the HTML. Omit it server-side.
- Say why, once. A disabled control needs its reason nearby (a hint, a tooltip, or a line above the action bar) — not on hover only, which touch and keyboard users never get.
- Degrade the screen, not each control. A viewer's screen should look deliberate — facts and a timeline — rather than an editor's screen with everything greyed out.
- Field-level permissions are a data contract. If the server may withhold a field, the pattern's data contract should say so; a silently missing field reads as a bug.