Server rendering
Every UniAria entry imports safely on a server: nothing touches document or
window when a module loads, and each entry is checked by importing it in
Node. Effects, timers and listeners start only in the browser.
useBehavior renders each part with its current props on the first render,
so the server HTML already has the roles, ARIA states, ids and state
attributes a component starts with: a selected checkbox’s checked, a
disabled button’s disabled, a listbox’s selected options.
- Ids come from React’s
useId, so they match between the server and the client. An id you write on a part after the spread is in the server HTML, but references to the part (a label’sfor) name its own id until hydration, when UniAria finds yours. Pass the id through the component’s prop (inputId,listIdand similar) for references that are right before hydration (see Ids). - Live values such as an input’s
valueand a checkbox’scheckedrender once, asdefaultValueanddefaultChecked, so the server HTML shows them and React never controls the element afterwards. - Owners found through
OwnerProviderresolve during the first render, so a radio’scheckedfrom its group’s value is in the server HTML. A child that finds its owner through the DOM links after hydration instead. - References to optional parts, such as a field’s
aria-describedbypointing at its description, are added once the part is in the DOM, in a layout effect before the first paint. The server HTML has everyidand the label’sfor. - Overlays are in the HTML, closed: a popover’s root has
popover="manual", which hides it, and a dialog isn’topen. One withdefaultOpenopens after hydration, since opening a modal<dialog>needs a browser.
renderToString from vue/server-renderer, and Nuxt with it, gives the same
HTML: useBehavior renders each part with its current props during setup
and the first render.
- Ids come from Vue’s
useId, so they match between the server and the client, with the same rule for an id you write afterv-bindas in React. An app’sidPrefixmust be the same on both sides. - Live values render once as
^valueand^checked, attributes only, so the server HTML shows them and Vue never controls the element afterwards. - Owners provided by the components around a child resolve during setup,
so a radio’s
checkedfrom its group’s value is in the server HTML. - References to optional parts and overlays work as in React: added once the part is in the DOM, and closed in the HTML.
Hydration attaches UniAria to the server’s elements without rendering again.
On the server, a custom element is just your markup. It works as HTML until
the element upgrades in the browser: a button submits its form, a link
navigates, a checkbox toggles and submits, a <dialog> stays closed.
When it upgrades, the element reads its starting state from that markup where
HTML has it: a checkbox’s checked, a text field’s value, a link’s href,
each part’s id, a list’s options and the label’s tag. Then it writes the
rest: roles that HTML can’t express (role="switch"), ARIA links between
parts, state attributes and keyboard handling.
Elements upgrade in any order. A child that connects before its owner (a radio before its group) waits, and links when the owner connects.
Everything UniAria writes after the first render goes straight to the DOM, without a render. Style the state attributes, not framework state, and the page looks right from the first paint after hydration.