Start here: If you’re migrating an app, read the beta tester guide first: MIGRATION.md
DOM behavior in Solid 2.0 follows HTML standards by default: attributes over properties, lowercase attribute names for built-ins, and consistent boolean handling. The class prop is enhanced (classList merged in, support for array/object for composition). These changes improve interoperability with web components and SSR and simplify the attribute/property story.
- HTML standards: Supporting multiple ways to set attributes/properties by type makes it harder to build on Solid (e.g. custom elements, SSR). Favoring attributes and lowercase aligns with the platform and removes special cases;
attr:andbool:namespaces are no longer needed. - class: Merging
classListintoclassand supporting array/object (clsx-style) reduces API surface and supports composition without extra helpers.
- Attributes over properties: Prefer setting attributes rather than properties in almost all cases. Aligns with web components and SSR.
- Lowercasing: Use HTML lowercase for built-in attribute names (no camelCase for attributes). Exceptions:
- Event handlers remain camelCase (e.g.
onClick) to keep theonmodifier clear. - Default to attributes But attributes such as
input.value,input.defaultValue,input.checked,input.defaultChecked,select.value,option.value,option.selected,option.defaultSelected,textarea.value,textarea.defaultValue,video.muted,video.defaultMuted,audio.muted,audio.defaultMutedcontinue to be handled as props where that avoids confusion. Unfortunately, this leads to all form fields be special cased. For example:<input value={dynamicCurrentValue()} defaultValue={dynamicDefaultValue()}/>either can be dynamic or static, and in the absense ofdefaultValue, then,valueis SSRed.
- Event handlers remain camelCase (e.g.
- Namespaces:
attr:andbool:namespaces are removed; the single standard behavior makes the model consistent. - XML Namespaces:
svgandmathwork as expected, however when using XML partials, anxmlnsattribute is required for the browser to create the elements with the correct namespace. Solid adds these automatically to the tags that can recognize as SVG/MathML. For example anatag returned from a partial to be used in XML needxmlnsadded by the user.
classListis removed; its behavior is merged intoclass.classaccepts: string, object (key = class name, value = truthy to apply), or array of strings/objects. Enables clsx-style composition. Example:
<div class="card" />
<div class={{ active: isActive(), disabled: isDisabled() }} />
<div class={["card", props.class, { active: isActive() }]} />- Boolean literals add/remove the attribute (no
="true"string). For attributes that require the string"true", pass a string. - Types are updated to reflect this.
// Presence/absence boolean attributes
<video muted={true} />
<video muted={false} />
// When the platform requires a string value:
<some-element enabled="true" />Solid 2.0 removes the use: directive namespace and instead treats “directives” as a first-class ref pattern. The ref prop becomes the single composition point for:
- DOM element access (
ref={el => ...}) - directive factories (
ref={tooltip(options)}) - composition (
ref={[a, b, c]}; arrays may be nested)
// 1.x: directive namespace
<input use:autofocus />
<button use:tooltip={{ content: "Save" }} />
// 2.0: directive/ref callbacks (factory form)
<input ref={autofocus} />
<button ref={tooltip({ content: "Save" })} />Multiple refs (or directives) can be applied by passing an array:
<button ref={[autofocus, tooltip({ content: "Save" })]} />The recommended directive pattern is two-phase, similar in spirit to “split effects” (compute phase vs apply phase):
- Setup phase (owned): run once to create reactive primitives and subscriptions. This phase should not perform imperative DOM mutation.
- Apply phase (unowned): receives the element and performs DOM writes (including reactive writes). This phase should not create reactive primitives.
function titleDirective(source) {
// Setup phase (owned): create primitives/subscriptions here
// but avoid imperative DOM mutation at top level.
let el;
createEffect(source, value => {
// Effect can run before the element is available
if (el) el.title = value;
});
// Apply phase (unowned): DOM writes happen here.
// No new primitives should be created in this callback.
return nextEl => {
el = nextEl;
el.title = source();
};
}Used as:
<button ref={titleDirective(() => props.title)} />- classList: Use
classwith an object or array instead. - Attributes: Use lowercase attribute names; use string
"true"only where the platform requires it. - Directives: Replace
use:foo={...}withref={foo(...)}(orref={foo}when no options are needed). Use an array when you need multiple directives/refs.
| Removed | Replacement / notes |
|---|---|
classList |
Use class with object or array |
oncapture: |
Removed; use native addEventListener with { capture: true } where needed |
attr: / bool: namespaces |
Single attribute/property model above |
use: directives |
Use ref callbacks / directive factories (ref={directive(opts)}); arrays compose (ref={[a, b]}) |
- Keeping
classListalongsideclasswas rejected to avoid two ways to do the same thing and to simplify the compiler/runtime. - Keeping camelCase for attributes was rejected in favor of HTML alignment and web component compatibility.
- Exact list of "default" props that stay as props (
input.value,input.defaultValue,input.checked,input.defaultChecked,select.value,option.value,option.selected,option.defaultSelected,textarea.value,textarea.defaultValue,video.muted,video.defaultMuted,audio.muted,audio.defaultMuted, …). Generally, stateful DOM properties should be considered on this list.