Snapshot 2026-10-31
§2 · Declarative HTML Components

Types

A declared type tells Declarative Components how to read a value written in HTML and what JavaScript value to give the component.

<prop name="count" type="integer"></prop>
<x-counter count="3"></x-counter>

Here the written count="3" becomes the number 3 in host.props.count.value. Type names are plain keywords. A prop can also declare constraints on values of its type.

Base value types

Each type has one name and one meaning.

TypeExampleDescription
string"Save", ""Any string, including the empty string.1
keywordcompact, size-2One or more ASCII letters, digits, underscores, or hyphens (^[A-Za-z0-9_-]+$), represented as a JavaScript string.2
booleantrue, falseA truth value.3
integer3, -2A whole number, parsed using HTML's integer syntax.4
number2.5A finite number, parsed using HTML's floating-point number syntax.4 Expression arithmetic on it is decimal to the operands' precision: 0.1 + 0.2 is 0.3.
urlhttps://example.org/A valid absolute URL string.5
emailada@example.orgA valid email address string.5
date2026-09-29A calendar date without a time zone, written year-month-day: at least four year digits and exactly two digits each for month and day.6
month2026-09A year and month without a time zone: at least four year digits, a hyphen, and a two-digit month.6
week2026-W40A week-year and week number: at least four year digits, -W, and a two-digit week.6
time13:45A time of day without a time zone: HH:mm, optionally followed by seconds and up to three fractional-second digits.6
datetime-local2026-09-29T13:45HTML's ISO 8601-derived local date and time format, without a time zone: a valid date, T or a space, then a valid time (YYYY-MM-DDTHH:mm for the example shown).6
datetime2026-09-29T13:45Z, 2026-09-29T13:45-07:00HTML's global date and time format: a valid date and time followed by Z or a numeric UTC offset.6
colorrebeccapurple, #663399, rgb(102 51 153)A CSS color literal: recognized color keywords, hex notation, or numeric rgb(), rgba(), hsl(), hsla(), hwb(), lab(), lch(), oklab(), oklch(), and color() forms. The value stays a string.7
color-hex#639, #663399, #663399ccA CSS hex color: # followed by 3, 4, 6, or 8 hexadecimal digits.8
length8px, 1remA CSS length value, kept in its serialized string form.9
percentage25%A CSS percentage value, kept in its serialized string form.9
duration200ms, 1.5sA CSS time value, kept in its serialized string form.9
Declared type rules

HTML already parses formats such as dates and URLs, and control modes change which values are valid. CSS defines colors, lengths, and other value grammars.116 JavaScript can implement these rules, but putting them only in component code hides them from the browser runtime, build tools, and generated TypeScript. Declarations expose the rules without changing native DOM properties such as HTMLInputElement.value.

In the JavaScript layer, number and integer prop values are JavaScript Number values. A written ratio="0.3" therefore becomes the number 0.3 in host.props.ratio.value when ratio is declared as number.

length, percentage, and duration remain serialized strings at that JavaScript boundary. An expression math function can calculate with their typed quantities and returns their written form; for example round(8.8px, 1px) produces 9px for a length destination.

Value constraints

Constraints narrow a declared type. They apply to <prop> declarations, including fields nested in an object or list. A valid value keeps its JavaScript type.

AttributeApplicable typesEffect when the value fails
requiredEvery typeA missing, null, or empty value sets valueMissing.
valuesScalar typesA value outside the permitted set sets typeMismatch.
patternstring, keyword, url, emailA nonempty value that does not match sets patternMismatch.
minlength, maxlengthstring, keyword, url, emailA nonempty value outside the inclusive length bounds sets tooShort or tooLong. Length counts UTF-16 code units, as in HTML.17
min, maxinteger, number, date, month, week, time, datetime-local, datetimeA nonempty value outside the inclusive bounds sets rangeUnderflow or rangeOverflow. Bounds use the declared type's written form.

An omitted optional prop without a default resolves to null; constraints other than required do not reject it. An empty string also skips those constraints. A constraint that does not apply to its declared type, or has an invalid value, is ignored by the live parser with a declaration warning; build tools report a declaration error. A default must satisfy every applicable constraint. See Validation for the resulting validity state.

Finite choices

The values attribute limits a prop to a comma-separated set of values of its declared type. Each item is parsed and checked against type before the constraint is applied. The prop keeps its one declared type. A TypeScript target can expose the allowed values as a literal union within that type. This follows HTML's enumerated attributes, whose permitted keywords are defined separately from the attribute's value syntax, and JSON Schema's enum constraint.1415

<prop name="size" type="keyword" values="sm, md, lg" default="md"></prop>
<x-button size="sm">Save</x-button>

<prop name="level" type="integer" values="1, 2, 3" default="2"></prop>
<x-meter level="3"></x-meter>

Whitespace around commas is ignored. An item that does not conform to type invalidates the whole values constraint, as though values were absent. Build tools, including the unplugin, report this as a declaration error. The live browser parser warns and ignores the constraint. A bound result must have the declared JavaScript type to be written; a result outside values is still written and sets typeMismatch. For type="integer" values="1, 2, 3", level="3" and from:level="3" produce the number 3; a bound result of 4 becomes 4 with typeMismatch, while from:level="'3'" skips its write because the expression produces a string. All declared props still accept null unless required.

Text patterns and lengths

HTML's pattern attribute provides a regular-expression constraint for text values. A declaration writes the pattern without / delimiters; the pattern must match the entire nonempty value. The value remains a string in JavaScript. required determines whether an empty or omitted value is allowed.10

<prop name="sku" type="string" pattern="[A-Z]{3}-[0-9]{4}"></prop>
<!-- Accepts ABC-1234. -->

<prop name="handle" type="keyword" minlength="3" maxlength="24"></prop>

Ranges

min and max compare values of the declared type. Neither attribute supplies a default or changes a value outside the range.

<prop name="quantity" type="integer" min="1" max="100"></prop>
<prop name="starts" type="datetime-local" min="2026-01-01T00:00"></prop>

An authored numeric bound is parsed as a number. Date and time bounds follow their type's written format. A date compares by calendar order; datetime compares instants using its UTC offset, while datetime-local compares local date and time without a time zone. The CSS dimension types (length, percentage, duration) have no min or max constraint here: comparing their written values would require unit conversion rules that these declarations do not define.

Lists and their written forms

Every list has one item type. These three declarations all produce a JavaScript array of keywords; they differ in how the value is written in an HTML attribute:

Declared typeWritten valueJavaScript value
list(keyword)['red', 'blue']["red", "blue"]
keyword+red blue["red", "blue"]
keyword#red, blue["red", "blue"]

list(T) uses a bracketed value, accepts an empty list ([]), and can give its items a structured type such as object({ id: number }). keyword+ and keyword# are shorter written forms for one or more keywords: space-separated and comma-separated, respectively, following CSS value definition syntax.12 They produce the same JavaScript array as list(keyword). Whitespace around a comma is allowed; an empty item is invalid.

Structured values

object({ ... }) describes named fields; list(T) requires every list item to have type T:

<prop name="point" type="object({ x: number, y: number })"></prop>
<prop name="rows" type="list(object({ id: number, name: string }))"></prop>

These values are written as object and bracketed list literals and become JavaScript objects and arrays. When a field needs its own values, required, or description, expand the shape into nested <prop> declarations. Write type="list" with one unnamed child <prop> for its item type. This expanded declaration describes the same rows type as list(object({ id: number, name: string })) above:

<prop name="rows" type="list">
  <prop type="object">
    <prop name="id" type="number" required></prop>
    <prop name="name" type="string" required></prop>
  </prop>
</prop>

The same field declarations describe event details and structured state values:

<event name="change" type="object">
  <prop name="value" type="number" required></prop>
  <prop name="trigger" type="keyword" values="keyboard, pointer, programmatic" required></prop>
  <prop name="previous" type="string"></prop>
</event>

<state name="history" type="list" value="[]">
  <prop type="object">
    <prop name="trigger" type="keyword" values="keyboard, pointer, programmatic" required></prop>
  </prop>
</state>

Each nested field is checked when the object or array is checked. A missing field and a null field are the same; previous above may be either, and required makes both invalid. See Null and missing values. An open object permits additional fields; a field with type="unknown" accepts any JavaScript value, but unknown cannot be a top-level prop because it has no HTML attribute form. A build tool reports a malformed nested values constraint as an error; the live browser parser warns and ignores that constraint. Generated TypeScript uses the closest literal types, so the event detail above has trigger: 'keyboard' | 'pointer' | 'programmatic'.

A shared or external schema can instead be referenced with schema, using JSON Schema:13

<prop name="rows" type="list" schema="/schemas/rows.json"></prop>

JSON Schema also has enum for a fixed set of values. An object schema can use it to constrain one field, and an enum can contain values of different JSON types.14 This JSON document is a JSON Schema:

{
  "type": "object",
  "properties": {
    "size": { "enum": ["sm", "md", "lg"] }
  }
}

Writing structured values

Structured values use the object and array literal syntax of HTML Next expressions. A plain attribute supplies a fixed value, parsed according to the declared prop type. A from: binding evaluates an expression that may read other values and change with them:

<x-plot point="{ x: 3, y: 5 }"></x-plot>
<x-table rows="[{ id: 1, name: 'Ada' }, { id: 2, name: 'Lin' }]"></x-table>
<x-plot from:point="{ x: $currentX, y: 5 }"></x-plot>

Bare keys, single-quoted strings, and trailing commas are allowed in both forms. A plain attribute contains only literal values; point="{ x: $currentX }" is invalid because $currentX is a reference. Use from:point="{ x: $currentX }" to read it, or from:point="$point" to pass an existing object from <state> or <data>. The from: prefix marks a binding. These expressions are pure and typed. JSON is used only as a wire format when structured data crosses an SSR, network, or interop boundary.

Native event values

The event type accepts a native DOM Event, including its subclasses such as PointerEvent, KeyboardEvent, and CustomEvent.18 It exists only as a JavaScript value. A handler obtains its triggering event through $$event and may pass it as an event payload or place it in a typed structure. Type checking does not clone the event or turn a plain object with similar fields into an event.

<event name="activate" type="event"></event>
<event name="selection-with-source" type="object">
  <prop name="source" type="event" required></prop>
  <prop name="item" type="string" required></prop>
</event>

In the first declaration the entire CustomEvent.detail must be a native event. In the second, detail is an object whose source is a native event and whose item is a string. A resource fetched as JSON cannot supply a native event. Mutable state can retain an event reference when its declaration permits it, but that reference does not make the native event's fields reactive or give it a serialized server representation. See The triggering event.

Request values

A request's detail and answer pass between JavaScript on both sides, so they exist only as JavaScript values. Their types are written as for an event's detail, and they may use types that a prop cannot:

<request name="extensions" returns="list(unknown)">Tiptap extensions added to the built-in set.</request>

<request name="image-upload" type="object">
  <prop name="file" type="unknown" required>The File the author dropped or pasted.</prop>
  <returns type="object">
    <prop name="src" type="string" required></prop>
  </returns>
</request>
  • unknown and event may appear at the top level of type and returns, since neither needs an HTML attribute form.
  • A JavaScript object outside these types, such as a File or a library's extension object, is typed unknown. An unknown value is passed through unchecked, and generated TypeScript types it as unknown.
  • An answer of null is always accepted: it means the consumer has no answer, as an omitted prop means no value. returns constrains every other answer.

The runtime checks the detail when a controller asks and the answer when it arrives; the request steps define both checks.

Types selected by a prop or state value

A component can select a prop's type from the current value of another declared prop or state value. For example, a type prop can select whether value is a string or a number.

An inline <type> belongs to the prop whose type varies:

<defs>
  <prop name="type" type="keyword" values="text, number" default="text">The control mode.</prop>
  <prop name="value">The control value.
    <type from="type">
      <option value="text" type="string"></option>
      <option value="number" type="number"></option>
    </type>
  </prop>
</defs>

The same type can be named once under <defs> and referenced by a declaration's type attribute. A named type is local to that component:

<defs>
  <type name="input-value" from="type">
    <option value="text" type="string"></option>
    <option value="number" type="number"></option>
  </type>
  <prop name="type" type="keyword" values="text, number" default="text">The control mode.</prop>
  <prop name="value" type="input-value">The control value.</prop>
</defs>

Selection rules

The selector has one base type and a finite values constraint:

  • from names one declared prop or state value. It is a name lookup.
  • Each permitted selector value needs exactly one <option>. The option's value is parsed as the selector's base type. An invalid values constraint makes the type declaration invalid.
  • A selecting prop must be required or have a default. Its effective value selects the type before the dependent attribute is parsed, regardless of attribute order. A selecting state uses its initialized value.
  • The dependent prop exists for every option. It is null when omitted without a default. A null selector also permits only a null dependent value because it selects no option.
  • A dependent prop may have a default only when its selector is a prop with a default. That dependent default must satisfy the selected type.

Reading and changing values

Plain attributes are parsed using the selected type. With the declaration above, type="number" value="2.5" produces the JavaScript number 2.5; type="text" value="2.5" produces the string "2.5".

  • Bound values keep their JavaScript type and must satisfy the selected option. For example, <x-input from:type="$mode" from:value="$entry"></x-input> can read two reactive values.
  • When the selector changes, the dependent type is selected again. Changing both values together checks the resulting pair. A previously supplied non-null dependent value that fails the new type is invalid; the component reports a type error instead of converting it.
  • Generated TypeScript preserves the relationship for a public prop selector: type="number" accepts a number or null for value, while type="text" accepts a string or null. Inline and named <type> declarations produce the same contract.

Selecting from state

The same name lookup can select from state:

<defs>
  <state name="mode" type="keyword" values="text, number" value="text"></state>
  <prop name="value">The current value.
    <type from="mode">
      <option value="text" type="string"></option>
      <option value="number" type="number"></option>
    </type>
  </prop>
  <handler name="useNumber"><set name="mode" value="number"></set></handler>
</defs>

The same selection rules apply as mode changes. A caller cannot know an internal state selector from the invocation alone, so generated TypeScript exposes all option types while runtime validation checks the active one. Here value has the public type string | number | null.

Null and missing values

A declared value keeps its type, as HTML's reflected attributes do. tabIndex is always a number: remove the tabindex attribute and the property gives back its default. A prop and a state work the same way. null means not present, as getAttribute() returns null for a missing attribute.11 It is distinct from the empty string, zero, and false.

  • A prop with a default, or a state with a value, always reads as its type. Writing null makes it not present, so it goes back to that default or value.
  • A prop without a default, or a state without value or from, reads null until it is given a value, and a null write stores null.
  • A state with from is unavailable until its first accepted seed or local write. A local null write ends that wait and stores null.
  • A missing field of an object reads null, or the absent value in expressions. Writing null to a field below a state gives it back its value in the state's declared value, or null where that has none.

undefined means the same as null wherever a value is written. A null write is therefore never a wrong-typed write; the declared type constrains every value that is present. Every declaration accepts null. required makes a value that is not present invalid: it sets valueMissing. An explicit empty string is present and remains a string value.

<!-- Given <prop name="label" type="string"></prop> and <prop name="tone" type="string" default="plain"></prop> -->
<x-note></x-note>                                    <!-- label is null; tone is "plain" -->
<x-note from:label="null" from:tone="null"></x-note> <!-- label is null; tone is "plain" -->
<x-note label=""></x-note>                           <!-- label is the empty string -->

<state name="count" type="integer" value="0">        <!-- always an integer; a null write gives back 0 -->
<state name="level" type="integer">                  <!-- null until written; a null write stores null -->

The same holds wherever the null comes from: a bound expression, a framework prop or componentRegistry.update for a prop; a controller, <set> or bind: for a state. A controller returns a state to its declared value with host.reset.

Future exploration

Unions could allow more than one base type. An untagged union of string and number needs a parsing rule: a written value of 2.5 could produce either a string or a number.

Sources

  1. WHATWG HTML, attribute values; Web IDL, DOMString.
  2. This proposal's ASCII rule is informed by WHATWG HTML's space-separated tokens and CSS's custom identifiers.
  3. Web IDL, the boolean type.
  4. WHATWG HTML, integers and floating-point numbers.
  5. WHATWG HTML, URL and email value formats.
  6. WHATWG HTML, dates, months, weeks, times, local dates and times, and global dates and times.
  7. CSS Color Module Level 4, color values.
  8. CSS Color Module Level 4, hexadecimal color notation.
  9. CSS Values and Units Level 4, lengths, percentages, and time values.
  10. WHATWG HTML, the pattern attribute; this proposal's Validation chapter applies pattern constraints to typed props.
  11. DOM Standard, getAttribute().
  12. CSS Values and Units Level 4, value definition syntax multipliers.
  13. JSON Schema, specification.
  14. JSON Schema Validation, the enum keyword.
  15. WHATWG HTML, keywords and enumerated attributes.
  16. CSS Values and Units Level 4, value definition syntax.
  17. WHATWG HTML, the minlength and maxlength attributes.
  18. WHATWG DOM, Event and CustomEvent: native event interfaces and custom payloads.

Declarative HTML Components Level 1: Type System. Unofficial Editor's Draft · Stage 0. Published in the HTML Next collection by the Next Web Working Group; not a W3C or WHATWG deliverable. Version history.