Skip to content

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.

Level
One switch for the whole region — every value read, not dimmed
vue
<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.

ts
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.

readonlystate="disabled"
SaysThis is the value. You may not change it.You could change this, but not now.
InkFull — the value is there to be readDimmed
BoxThe raised surface, no border to click intoThe disabled fill, its border kept
Placeholder, chevrons, steppers, clear buttonsNot drawnDrawn, greyed
Focus and selectionFocusable, selectable, copyableSkipped
Announced asread-onlydimmed / 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.

vue
<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 taking disabled, so they stay focusable.
  • HabRadio is the exception: ARIA allows aria-readonly on the group, not on a radio, so put it on your role="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 ​

PropTypeDefaultDescription
readonlybooleantrueWhether 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 ​

SlotBindingsDescription
defaultanyThe region. Every Habitus form control in it reads readonly from here.

Proprietary — internal use only.