Skip to content

set_attributes doesn't prioritize type before value for input elements, causing newline stripping with spreads #18332

Description

@Stadly

Describe the bug

When spreading props onto an <input> element and explicitly setting type="hidden" after the spread, newlines in the value are silently stripped on initial client-side render.

Minimal case:

<script>
  const spread = $state({ name: "field", value: "line1\nline2\nline3" });
</script>

<!-- BUG: newlines stripped -->
<input {...spread} type="hidden" />

<!-- OK: newlines preserved -->
<input type="hidden" {...spread} />

Root cause

set_attributes() in src/internal/client/dom/elements/attributes.js iterates the merged props object with for (const key in next). JavaScript iterates object keys in insertion order, so for:

{ ...spread(), type: "hidden" }

value (from the spread) is visited before type. At that point the element is still type="text" (the default). Setting .value on a text input triggers the HTML value sanitization algorithm, which strips \n and \r. By the time type is set to "hidden", the newlines are permanently lost.

The reverse order ({ type: "hidden", ...spread() }) works because type appears first in insertion order and is set before value.

Suggested fix

When set_attributes encounters both type and value for an <input> element, it should set type before value regardless of iteration order. This mirrors what React does — React's DOM reconciler explicitly sets type before other properties on input elements to avoid this class of bugs.

A minimal fix could be:

// In set_attributes(), before the main loop:
if (element.nodeName === 'INPUT' && 'type' in next) {
  const type = next.type;
  if (type !== element.type) {
    element.type = type;
  }
}

Related

This bug directly affects the workaround mentioned in sveltejs/kit#15887, where <input {...form.fields.id.as("text")} type="hidden" /> is suggested as an alternative to as("hidden", value). That pattern relies on type="hidden" overriding the spread, but due to this bug the attribute ordering causes silent data corruption — newlines in the value are stripped before type is applied. Until this is fixed, the only safe ordering is <input type="hidden" {...spread} />.

Impact

This affects any pattern where hidden form inputs are created with spread props — a common pattern in form libraries. It's particularly insidious because:

  1. It only affects client-side rendering (SSR hydration preserves newlines since the server renders correct HTML)
  2. It's completely silent — no warning, no error
  3. It causes data corruption (newlines permanently lost on form submission)

Reproduction

https://svelte.dev/playground/fed81261ad3f49ebaf66f41a16914264?version=5.56.0

Logs

No response

System Info

Svelte 5.56.0 (and likely all Svelte 5 versions using set_attributes)

Severity

annoyance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions