CalmUI
Components

Pass Values to Components

Use props to give each copy of a component its own text and options.

A prop is a value passed into a component. For example, the same MenuButton can show Play in one place and Settings in another.

In a View that imports MenuButton:

<MenuButton label="Play" />
<MenuButton label="Settings" />

Inside MenuButton, read the value:

<button>{{ props.label }}</button>

The component tutorial includes the full files.

Pass a changing value

A plain attribute supplies text. Prefix an attribute with : to read an expression instead:

<MenuButton :label="props.nextLabel" />

Here the parent must receive nextLabel. Props can also contain numbers, true/false values, arrays, or objects. A component can read them but should send an event or action when it needs the owner to change them.

In defineComponent, props is a live, read-only object. Read changing values inside computed() or watch() so later parent updates invalidate their dependencies. A parent update replaces its prop values as one batch: watchers see the complete new values, not a partially updated set. Unrelated prop changes do not invalidate computations that did not read them. Nested objects and arrays are read-only snapshots too; ask the owner to replace them through an event. Destructuring a scalar prop once during setup captures its current value rather than creating a reactive binding.

A returned script binding may evaluate to null. It is an intentional empty value, not a missing variable. A missing or undefined binding still produces a diagnostic. Use null when forwarding an optional value that the child owns a default for.

Translated text props

Use label="$loc('GameUI::Confirm', 'Potwierdź')" where you pass the prop. The component still reads {{ props.label }}. Text and its source reference pass through nested components without a text slot or JavaScript wrapper. See Translate Your UI for Languages, defaults and Culture.

Handle an optional value

Guard optional content with ui:if:

<p ui:if="props.description">{{ props.description }}</p>

If description is omitted, the paragraph is not created. Reading a missing prop in unguarded text or ordinary bound attributes produces an error.

Add an optional visual variant

<button class="menu-button"
        :class="{ 'menu-button--danger': props.variant == 'danger' }">
  {{ props.label }}
</button>

Define both classes in the component CSS. The base class always remains. If variant is omitted, no modifier is added. The label prop is still required.

Bind inline styles

:style accepts an object the same shape as :class, but its values become CSS declarations instead of class-name toggles:

<div style="border-style:dashed"
     :style="{ borderTopStyle: 'solid', '--accent-color': props.accent, display: props.hidden }">
</div>
  • Keys are camelCased (borderTopStyle → border-top-style); a key starting with -- is kept as-is as a CSS custom property.
  • null and false values omit that declaration entirely; every other value (including true) is written out with String()-style formatting.
  • A number value gets an implicit px unit appended (the same convention as React/Vue), so { width: 77 } becomes width:77px;. A bare number without a unit is invalid CSS and is silently dropped, leaving the element at its CSS default (e.g. a width reads as the parent's full width). The properties whose CSS Profile value is itself a bare number are never coerced: opacity, z-index, flex-grow, flex-shrink, line-height, font-weight, animation-iteration-count, and any --custom-property. A string value (already carrying its own unit, e.g. '50%') is never suffixed.
  • A static style="..." attribute on the same element is kept and always applies first; the bound object's declarations are appended after it, so a bound value overrides a static one for the same property, independent of which one is written first in the template, the same static-first/bound-wins contract as :class.
  • :style also accepts a plain string expression (e.g. :style="computedCss") instead of an object, appended after any static style the same way.

Like :class, a :style object value is a full R5 expression (literal, prop path, comparison, &&/||, $loc(...)), not JavaScript template literals or + concatenation. See the small binding profile below. Compute a full string with a unit (e.g. ${width}px) in <script> and bind the plain identifier instead:

const boxStyle = computed(() => ({ width: `${store.panelWidth}px` }));
<div :style="boxStyle"></div>

The small binding profile

An R5 template expression ({{ }}, :attr="...", :class, :style, event payloads) accepts only literals, props/local paths, comparisons, &&/||, and $loc(...), never a function call or + string concatenation. Writing 'prefix-' + value or a `${value}px` template literal in a binding fails to compile with CUI2050. Precompute the value in <script> instead:

const audioMasterPercent = computed(() => percentText(store.settings.audioMaster));
<p>{{ audioMasterPercent }}</p>

This keeps every binding a plain read with no compiler-hosted string formatting or arbitrary code. See docs/calmui-component-format.md for the full expression grammar this page summarizes.

On this page