Skip to content

Template System

Unified template evaluation for all LCARdS cards and text fields.


Overview

All dynamic text in LCARdS is processed by UnifiedTemplateEvaluator. It orchestrates four distinct sub-evaluators in a fixed priority order. Cards create a single evaluator instance and call evaluateAsync() on any string.


Key Classes

ClassFileRole
UnifiedTemplateEvaluatorcore/templates/UnifiedTemplateEvaluator.jsOrchestrator — runs all four evaluators in order
LCARdSCardTemplateEvaluatorcore/templates/LCARdSCardTemplateEvaluator.jsHandles JS [[[...]]] and token {...} templates
DataSourceTemplateEvaluatorcore/templates/DataSourceTemplateEvaluator.jsHandles {ds:name} and {datasource:name.key}
HATemplateEvaluatorcore/templates/HATemplateEvaluator.jsSends Jinja2 to HA server for evaluation
TemplateDetectorcore/templates/TemplateDetector.jsFast type detection — avoids unnecessary evaluator calls
TemplateParsercore/templates/TemplateParser.jsTokenises complex mixed-content strings

Template Types & Syntax

1 — JavaScript [[[...]]]

yaml
text: "[[[return entity.state.toUpperCase()]]]"
text: "[[[return Math.round(entity.attributes.brightness / 2.55) + '%']]]"

Evaluation context includes: entity, hass, config, theme, states.

2 — LCARdS Tokens {...}

yaml
text: "{entity.state}"
text: "{entity.attributes.friendly_name}"
text: "{theme:palette.moonlight}"
text: "{config.name}"

3 — DataSource {ds:...}

yaml
text: "{ds:sensor_name}"                    # HA-native: locale-formatted + unit
text: "{ds:sensor_name.celsius:.1f}"        # processor buffer: 1 decimal, no auto-unit
text: "{ds:sensor_name.celsius:.1f} °C"    # manual unit suffix
text: "{datasource:temp.rolling_avg}"       # explicit long prefix

Rule: no format spec → HA-native display with unit. With a format spec → number only, caller controls the suffix.

4 — Jinja2 {{...}} / {% %}

yaml
text: "{{states('sensor.temp')}} °C"
text: "{% if is_state('light.kitchen','on') %}On{% else %}Off{% endif %}"

Jinja2 is asynchronous — evaluated by HA server via WebSocket.


Evaluation Order

A single string may contain multiple template types. They are evaluated sequentially:

  1. JavaScript [[[...]]] replaced first (synchronous)
  2. Token {...} replaced (synchronous)
  3. DataSource {ds:...} replaced (synchronous — reads latest buffer value)
  4. Jinja2 {{...}} sent to HA (async — awaited before render)

Creating an Evaluator

javascript
import { UnifiedTemplateEvaluator } from '../core/templates/UnifiedTemplateEvaluator.js';

const evaluator = new UnifiedTemplateEvaluator({
  hass: this.hass,
  context: {
    entity: this.hass.states[this.config.entity_id],
    config: this.config,
    hass: this.hass,
    states: this.hass.states,                 // flat dict keyed by full entity_id
    variables: this.config.variables || {},
    theme: window.lcards.core.themeManager.getActiveTheme?.()
  },
  dataSourceManager: window.lcards.core.dataSourceManager
});

const result = await evaluator.evaluateAsync(template);

evaluateAsync() runs all four phases (including Jinja2). A second public entry point, evaluateSync(), runs only JS + Token + DataSource — no Jinja2, since that requires the async HA round-trip — for callers that need a result without an await (e.g. a value recomputed on every hass tick, like lcards-slider.js's marker value/min/max resolution).

The states.<domain>.<object_id>.state / .attributes.<key> token form resolves a different entity's state via the states context key above. Because hass.states is a flat dict keyed by the whole "domain.object_id" string (not a nested object), LCARdSCardTemplateEvaluator._resolveToken() special-cases this form — reconstructing the entity ID from the two path segments right after states — rather than relying on its normal per-segment dot-walk.


See Also