Props

When you write reactolith components in HTML, attributes are mapped to React props using the following rules:

HTML attribute React prop / value Notes
name="test"name: "test"Strings pass through unchanged
enabled (no value)enabled: trueEmpty / boolean attributes become true
data-foo="baa"dataFoo: "baa"On a custom-component tag, normalized to camelCase
data-foo="baa"data-foo: "baa"On a native element, kept as data-* for the DOM
aria-label="x"aria-label: "x"On a native element, aria-* is passed through verbatim so it lands on the DOM unchanged
class="x"className: "x"class is automatically renamed to className
for="email"htmlFor: "email"Common lowercase HTML attributes on native elements (for, tabindex, colspan, rowspan, maxlength, readonly, enctype, crossorigin, viewbox, …) are mapped to their React camelCase names
json-config='{"x":1}'config: { x: 1 }Attributes prefixed with json- are JSON-parsed
as="{my-other}"as: <my-other />Wrapping a value in {...} resolves it to a React component
key="abc"(used as React key)Reserved — not passed as a prop
#anything="…"(ignored)Attributes starting with # are dropped

Example

<my-component enabled name="test" data-foo="baa" as="{my-other-component}" json-config='{ "foo": "baa" }' ></my-component> const props = { enabled: true, name: "test", dataFoo: "baa", // dash → camelCase as: <MyOtherComponent />, config: { foo: "baa" }, // parsed from JSON };

Encoding props server-side

The table above is the client-side contract — how reactolith reads HTML and turns it into React props. The mirror image is the server-side contract: how your backend has to serialize a JavaScript-shaped prop value into an HTML attribute so it round-trips correctly.

Prop value (server) HTML attribute (rendered) Notes
"text"name="text"Plain strings
42name="42"Numbers are stringified
truenameBare attribute — not name="true", which would arrive as the string "true"
false / null / undefined(omit entirely)Don't render the attribute at all
{ x: 1 }json-name='{"x":1}'Prefix the attribute name with json- and JSON-encode the value
[1, 2, 3]json-name='[1,2,3]'Same rule for arrays

The rule set is small enough that any backend can ship its own helper — a single function that walks an associative array and emits the right attribute syntax per value type. Reactolith itself doesn't care which framework you use, only that the resulting HTML follows the table above.

Reference helper (any language)

function renderAttrs(props: Record<string, unknown>): string { const out: string[] = []; for (const [name, value] of Object.entries(props)) { if (value === false || value == null) continue; if (value === true) { out.push(name); continue; } if (typeof value === "object") { out.push(`json-${name}='${JSON.stringify(value).replace(/'/g, "&#39;")}'`); continue; } out.push(`${name}="${String(value).replace(/"/g, "&quot;")}"`); } return out.join(" "); }

If a json-* attribute contains invalid JSON, reactolith logs a warning and passes the prop as undefined instead of throwing. The App instance also emits a "json-parse:failed" event so you can ship the failure to your telemetry pipeline:

app.on("json-parse:failed", (error, { attrName, value, element }) => { // forward to Sentry / log aggregator / whatever console.error("malformed prop", attrName, value, error); });