HabReadonly
Makes every Habitus form control inside it read-only: each shows its value to be read, and offers no way to change it. For a reader without write permission, a record someone else owns, a value locked by something upstream.
<HabReadonly :readonly="!canEdit">
<HabInput v-model="name" label="Name" />
<HabSelect v-model="kind" label="Kind" :options="kinds" />
</HabReadonly>It renders nothing of its own — no box, no spacing — so it can wrap a whole form, a panel body or a single row.
What it reaches
HabInput, HabSelect, HabAutocomplete, HabTreeSelect, HabSegmented, HabCheckbox, HabRadio and HabSwitch, and HabFieldAddon in a field's prepend segment, which shows its label and opens nothing.
HabChipInput and HabColorPicker take no readonly yet and stay editable inside a region. Lock them with their own disabled meanwhile, or leave them out of a read-only view.
Your own controls
A control of your own inside the region reads it with useHabReadonly(), so it locks with the Habitus controls around it rather than through a second flag of its own. Hand it the component's own readonly prop to give it the same precedence: set, the prop wins, false included; left undefined, the region decides.
import { useHabReadonly } from "@livereader/habitus-vue";
const props = withDefaults(defineProps<{ readonly?: boolean }>(), {
readonly: undefined,
});
const isReadonly = useHabReadonly(() => props.readonly);The undefined default is not optional: Vue turns an absent boolean prop into false, which would read as "editable" and override the region.
Read-only is not disabled
They answer different questions, and look different on purpose.
readonly | state="disabled" | |
|---|---|---|
| Says | This is the value. You may not change it. | You could change this, but not now. |
| Ink | Full — the value is there to be read | Dimmed |
| Box | The raised surface, no border to click into | The disabled fill, its border kept |
| Placeholder, chevrons, steppers, clear buttons | Not drawn | Drawn, greyed |
| Focus and selection | Focusable, selectable, copyable | Skipped |
| Announced as | read-only | dimmed / unavailable |
Reach for disabled when the reason is temporary and the user can do something about it — fill in another field first, wait for a save. Reach for read-only when they cannot: the value is not theirs to change.
WCAG exempts disabled controls from contrast, which is exactly why a disabled field is the wrong way to show a value someone needs to read.
One control, or a region
Every form control takes readonly itself. Unset, it follows the nearest surrounding HabReadonly; set to false, it stays editable inside one — a comment field on an otherwise locked record.
<HabReadonly>
<HabInput v-model="title" label="Title" />
<HabInput v-model="note" label="Your note" :readonly="false" />
</HabReadonly>Error and disabled outrank read-only: a field that is both in error and read-only keeps its error border, because the reader still needs to know.
It only presents
A read-only control offers no way to change its value, but it is not a permission check. Refuse the write where it happens — on the server, and in the action that would send it.
A control that is not a Habitus component is not reached by the region. Give it the same look with the read-only box, border-transparent bg-surface-2 text-fg, and the same rule: no placeholder, nothing that only writes.
Accessibility
- Native fields carry
readonly, which browsers announce and which keeps them in the tab order: a keyboard user can still reach, read and copy the value. - Controls without a native read-only state set
aria-readonly="true"and ignore input rather than takingdisabled, so they stay focusable. - HabRadio is the exception: ARIA allows
aria-readonlyon the group, not on a radio, so put it on yourrole="radiogroup"element. See HabRadio. - The region says nothing itself. Say WHY it is read-only once, near it — "You can view this but not change it" — rather than on every field.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
readonly | boolean | true | Whether everything inside is read-only. Bind it to the reason — a missing permission, a locked record — so the region follows it. A control inside can still opt out with its own :readonly="false". |
Slots
| Slot | Bindings | Description |
|---|---|---|
default | any | The region. Every Habitus form control in it reads readonly from here. |