Patterns
Disabled and read-only
The difference between disabled and read-only, and when to use each.
Disabled and read-only say different things. Disabled says “unavailable right now”; read-only says “you can see this value but not change it”. This pattern keeps the two apart.
When to use
Section titled “When to use”- When an action or field is temporarily unavailable: disabled.
- When the user may see and copy a value but not change it: read-only.
- Do not show a control the user can never use; hide it.
| Property | Disabled | Read-only |
|---|---|---|
| Focusable | No | Yes |
| Submitted with the form | No | Yes |
| Copyable | No | Yes |
| Screen reader | Announced as dimmed or unavailable | Announced as “read only” |
| Contrast requirement | Exempt | Normal |
| When to use | A temporarily unavailable action or field | A value the user may see but not change |
Live example
Section titled “Live example”Tab through the example: the read-only input and the explained button take focus; the disabled input does not. The same data also appears as a view page below.
Read-only and disabled field
Disabled action and setting
Off because no team is added. Add people on the Team page first.
The same data on a view page
- Project code
- PRJ-1024
- Owner
- Team A
- Records
- 12
import { Button, DescriptionList, Heading, Stack, Switch, TextInput, Tooltip } from '@bigbang/ui';import { type Lang, LangProvider } from '../lang';
const copy = { tr: { readOnlyTitle: 'Salt okunur ve devre dışı alan', readOnly: 'Salt okunur', disabled: 'Devre dışı', value: 'PRJ-1024', actionTitle: 'Devre dışı eylem ve ayar', publish: 'Yayımla', publishReason: 'Yayımlamak için önce bir başlık ekleyin', notify: 'Ekibe bildirim gönder', notifyReason: 'Ekip eklenmediği için kapalı. Önce Ekip sayfasından kişi ekleyin.', viewTitle: 'Görüntüleme sayfasında aynı veri', items: [ { term: 'Proje kodu', details: 'PRJ-1024' }, { term: 'Sahibi', details: 'Ekip A' }, { term: 'Kayıt sayısı', details: '12' }, ], }, en: { readOnlyTitle: 'Read-only and disabled field', readOnly: 'Read only', disabled: 'Disabled', value: 'PRJ-1024', actionTitle: 'Disabled action and setting', publish: 'Publish', publishReason: 'Add a title before you publish', notify: 'Notify the team', notifyReason: 'Off because no team is added. Add people on the Team page first.', viewTitle: 'The same data on a view page', items: [ { term: 'Project code', details: 'PRJ-1024' }, { term: 'Owner', details: 'Team A' }, { term: 'Records', details: '12' }, ], },};
/** * Read-only keeps the value in the tab order, copyable and submitted; disabled removes it from all three. * A disabled action explains why: a focusable Button with a Tooltip, or a Switch with a description. */export function StateComparison({ lang = 'tr' }: { lang?: Lang }) { const t = copy[lang]; return ( <LangProvider lang={lang}> <div className="w-full max-w-md"> <Stack gap="2xl"> <Stack gap="md"> <Heading level={3} size="sm"> {t.readOnlyTitle} </Heading> <TextInput label={t.readOnly} name="code-readonly" readOnly defaultValue={t.value} /> <TextInput label={t.disabled} name="code-disabled" disabled defaultValue={t.value} /> </Stack> <Stack gap="md"> <Heading level={3} size="sm"> {t.actionTitle} </Heading> <div> <Tooltip content={t.publishReason}> <Button disabled focusableWhenDisabled> {t.publish} </Button> </Tooltip> </div> <Switch label={t.notify} description={t.notifyReason} disabled /> </Stack> <Stack gap="md"> <Heading level={3} size="sm"> {t.viewTitle} </Heading> <DescriptionList items={t.items} dividers /> </Stack> </Stack> </div> </LangProvider> );}| Rule | Value |
|---|---|
| A control the user can never use | Do not disable it; hide it. |
| A disabled action | Say why: a focusableWhenDisabled Button with a Tooltip giving the reason, or text next to it. |
| Submit button | Never disabled to signal validation. See Forms and validation. |
| View pages | Show read-only values as a DescriptionList, not as read-only inputs. |
| Disabled Switch | Write the reason in description. |
| Contrast | A disabled item is exempt; a read-only value is read at normal contrast (4.5:1). |
Behaviour
Section titled “Behaviour”- The user reaches a disabled control. If it is a button that takes focus, the Tooltip shows the reason.
- When the reason goes away, for example after a title is added, the control becomes enabled; no extra announcement is needed.
- A read-only field takes focus, can be selected and copied; typing does not change the value.
- A read-only field is submitted with the form; a disabled field is not. If the value must be sent, use read-only.
Accessibility
Section titled “Accessibility”- A
focusableWhenDisabledButton hasaria-disabled="true"and stays in the tab order; the Tooltip links the reason witharia-describedby. - A read-only field has
readonlyand is announced as “read only” by screen readers. - A disabled field has
disabledand leaves the tab order. The reason should not live only in a Tooltip; add text next to it when needed. - Disabled items are exempt from the WCAG 1.4.3 contrast requirement, so never carry important information in them.
- The Tooltip opens on keyboard focus as well as on hover.
Content
Section titled “Content”- The reason is short and points to a fix: “Add a title before you publish”.
- Do not write text that repeats the state, such as “Disabled” or “Unavailable”; write the reason.
- A Switch description gives the state and the way out: “Off because no team is added. Add people on the Team page first.”
Motion
Section titled “Motion”| Item | Motion | Preset |
|---|---|---|
| Field border and fill | 110 ms colour change | motion.field |
| Tooltip | 150 ms open, 110 ms close | motion.tooltip |
| Button | 0.97 scale on press when enabled; no motion when disabled | motion.press |
With reduced motion, movement is removed and only the fade stays.
Components used
Section titled “Components used”TextInput, Button, Tooltip, Switch, DescriptionList. Related pattern: Forms and validation.