Behavior without the look
@markless/ui gives you menus, dialogs and pickers that already handle keyboard, focus and screen readers, and leaves the look to you.
Your configuration already lists ui(). So what does @markless/ui put in your app?
A dialog takes ten minutes to draw. Then Escape does nothing, focus slips behind it, and a screen reader never hears it open.
@markless/ui handles that hard part. It ships 46 component families. Each one owns state, keyboard, focus and screen reader labels. It owns no colors, fonts or spacing. You bring those with your own CSS.
A family is a set of parts. The root part holds the state. The other parts read it, and most of them show it on their own element as a ui-* attribute.
Click Edit address. Then press Escape.
What changes when the modal opens?
Press Edit address. Then press Escape, or Cancel. Watch which parts light up.
Deliver to: 12 Elm Street
Edit delivery address
import { modal } from '@markless/ui'; export default function EditAddress() @{ <modal.root> (ui-closed) <modal.trigger>Edit address</modal.trigger> (ui-closed) <modal.backdrop> (ui-closed) <modal.content> (ui-closed) <modal.title>Edit delivery address</modal.title> <modal.close>Cancel</modal.close> </modal.content> </modal.backdrop> </modal.root>}- modal.rootui-closed
- modal.triggerui-closed
- modal.backdropui-closedhidden
- modal.contentui-closed
- modal.titleno ui-* attribute
- modal.closeno ui-* attribute
The modal is closed. Nothing has changed yet.
Simplified. The real modal covers the whole page. Here it covers only this small window. The part names and attributes match the modal's own source.
Notice that one change of open marked four parts at once. Escape also put focus back on Edit address.
import { modal } from '@markless/ui';
export default function EditAddress() @{
<modal.root>
<modal.trigger>Edit address</modal.trigger>
<modal.backdrop>
<modal.content>
<modal.title>Edit delivery address</modal.title>
<modal.close>Cancel</modal.close>
</modal.content>
</modal.backdrop>
</modal.root>
}
You wrote no handlers. The modal’s own tests check that:
- Escape closes it and puts focus back on the trigger.
- While it is open, the page behind it is out of reach for the mouse, keyboard and screen readers.
- Tab never moves focus to the page behind it.
modal.titlebecomes the name a screen reader announces.
Each part name is one lowercase word. select.itemtrigger is the trigger of one item. slider.valuelabel is a label that shows the value.
Next: How do these parts get into your app? Using components →
