Shadow DOM creates a challenge for HTML forms: form controls inside a Shadow
Root are invisible to the parent <form> element. su provides options to
bridge this gap.
By default, a Custom Element with Shadow DOM is not a form participant. If you
place <my-input> inside a <form>, the form won't see its value.
Set formAssociated to true in the component options to make it a
form-associated Custom Element:
(defc my-input
{:props {:name "string" :value "string"}
:formAssociated true}
[{:keys [name value]}]
[:input {:type "text"
:name name
:value value}])With formAssociated: true, the browser treats the Custom Element as a form
control. It can participate in form submission, validation, and the
FormData API.
Set delegatesFocus to true to delegate focus into the Shadow Root:
(defc my-input
{:props {:placeholder "string"}
:delegatesFocus true}
[{:keys [placeholder]}]
[:input {:type "text"
:placeholder placeholder}])When the Custom Element receives focus (e.g., via tab navigation or
programmatic .focus()), focus is automatically forwarded to the first
focusable element inside the Shadow Root.
This improves keyboard navigation and accessibility.
- Use semantic HTML inside components —
<button>,<input>,<label>, not styled<div>elements - Add labels — pair inputs with
<label>elements or usearia-label - Keyboard support — handle
on-keydownfor Enter/Escape in custom interactions - Focus management — use
delegatesFocusfor input components
(defc accessible-input
{:props {:label "string" :name "string"}
:delegatesFocus true
:formAssociated true}
[{:keys [label name]}]
[:div
[:label {:for "field"} label]
[:input {:id "field"
:type "text"
:name name
:aria-label label}]])| Option | Purpose |
|---|---|
formAssociated |
Make component participate in <form> |
delegatesFocus |
Forward focus into Shadow Root |