Props

Props are a component's public attributes. Declare them with defineProps and the prop builder, which converts attribute text into typed values, validates them and supplies defaults.

const props = defineProps({
  heading: prop.string().default("Todos"),
  count: prop.number().int().min(0).default(0),
  variant: prop.enum(["primary", "ghost", "danger"]).default("primary"),
  disabled: prop.boolean(),
  tags: prop.array(prop.string()).default([]),
  user: prop.object({ name: prop.string(), email: prop.string().email() }).optional(),
  href: prop.string().url().required(),
  size: prop.number().attribute("data-size").reflect(),
});

defineProps returns a reactive object. Read it in the script (props.count) and in markup with @ references:

<h2><slot name="@heading"></slot></h2>
<button _disabled="@disabled">Save</button>
Previewindex.htmlprice-tag.html
Sale
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Props demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/badge.html"></import-element>

  <import-element from="./price-tag.html"></import-element>

  <price-tag amount="19.5"></price-tag>
  <price-tag amount="12" currency="EUR" sale></price-tag>
  <price-tag amount="-3" currency="JPY"></price-tag>
</body>
</html>
<custom-element name="price-tag">
  <script>
    const props = defineProps({
      amount: prop.number().min(0).default(0),
      currency: prop.enum(["USD", "EUR", "JPY"]).default("USD"),
      sale: prop.boolean(),
    });

    const price = useState({
      get label() {
        return new Intl.NumberFormat("en", { style: "currency", currency: props.currency }).format(props.amount);
      },
    });
  </script>

  <style>
    :host {
      display: inline-flex;
      gap: calc(var(--spacing) * 2);
      align-items: center;
      font-variant-numeric: tabular-nums;
    }
  </style>

  <strong><slot name="#price.label"></slot></strong>
  <ui-badge variant="destructive" _hidden="!@sale">Sale</ui-badge>
</custom-element>

The third tag has an invalid amount. REALM logs a warning naming the component, the prop and the rule, then uses the default (0). Invalid values never crash a component.

Types

Builder Attribute text is converted with Rules
prop.string() As-is .min(n), .max(n) (length), .pattern(regex), .email(), .url()
prop.number() Number(text). Empty text is invalid .int(), .min(n), .max(n)
prop.boolean() Native rule: present is true, absent is false
prop.enum([...]) Must be one of the listed strings
prop.date() new Date(text), usually ISO text Must be a valid date
prop.array(item?) JSON.parse(text) Each item is checked against item
prop.object(shape?) JSON.parse(text) Each key in shape is checked

Arrays and objects are usually passed as properties, not written by hand as JSON. See Passing objects.

Modifiers

Modifier Meaning
.default(value) Used when the attribute is missing or its value is invalid. Object and array defaults are cloned per instance
.required() A missing value is reported with a warning
.optional() Allows undefined
.reflect() Setting the property also writes the attribute (false, null and undefined remove it, true writes an empty attribute)
.attribute("name") Use a different attribute name. By default maxItems maps to max-items

Without a default, a missing prop is undefined, except booleans, which are false.

Attributes and properties

Each prop is both an attribute and a property on the element, like native elements:

const tag = document.querySelector("price-tag");
tag.amount = 25; // property: validated, not written to the attribute
tag.setAttribute("amount", "30"); // attribute: parsed, then validated

Writing props from inside

props is read-mostly. Writing a declared prop from the component goes through the same validation as a property write, and reflects if the prop reflects:

const props = defineProps({ open: prop.boolean().reflect() });

function toggle() {
  props.open = !props.open;
}

Writing a name that is not declared logs a warning and does nothing. Use useState for internal values.

Passing objects

A _ binding to another REALM component passes objects and arrays as properties, so they arrive intact instead of being turned into text:

<user-card _user="#session.user" _compact="@compact"></user-card>
// inside user-card
const props = defineProps({
  user: prop.object({ name: prop.string(), email: prop.string().email() }),
  compact: prop.boolean(),
});

Strings, numbers and booleans are written as attributes, which the child parses with its own prop types.

Props in CSS

Every prop can be read in the component's styles as var(--attr-name) and matched with :state(attr-name) or :state(attr-name-is-value):

:host(:state(attr-variant-is-danger)) button {
  background: var(--destructive);
}

See Styling.

One call per component

defineProps may be called once per script. A second call is reported as an error. Put every prop in one schema.

Edit this page on GitHub · View as Markdown