The Unified Template System allows you to control multiple display properties from a single Jinja2 template using JSON responses. This replaces the need for multiple separate template boxes and enables seamless entity remapping with entity context variables.
- One Template Instead of Many - Control icon, color, name, and more from one place
- Entity Context Variables - Templates automatically work with any entity (no hardcoded entity IDs)
- Seamless Entity Remapping - Change entity, template still works
- Cleaner UI - No more cluttered template sections
- Backward Compatible - Legacy templates still work
- Icon Module
- Info Module
- Text Module
- Markdown Module
- Bar Module
- Graphs Module
- Spinbox Module
- Camera Module
When you write a unified template, these variables are automatically available:
| Variable | Description | Example Value |
|---|---|---|
entity |
Entity ID | 'sensor.temperature' |
state |
Current state value | '23.5' |
name |
Custom name or friendly name | 'Living Room Temp' |
attributes |
Full attribute object | { unit_of_measurement: '°C' } |
unit |
Unit of measurement | '°C' |
domain |
Entity domain | 'sensor' |
device_class |
Device class | 'temperature' |
friendly_name |
HA friendly name | 'Living Room Temperature' |
config |
Icon/entity configuration | { icon_inactive: 'mdi:thermometer' } |
state_number |
State as number | 23.5 |
state_boolean |
State as boolean | true |
Return a simple string to update just the icon:
{% if state | int > 25 %}
mdi:fire
{% else %}
mdi:snowflake
{% endif %}Return JSON to control multiple properties:
{% set temp = state | int %}
{
"icon": "{% if temp > 25 %}mdi:fire{% else %}mdi:snowflake{% endif %}",
"icon_color": "{% if temp > 25 %}#FF0000{% else %}#0000FF{% endif %}"
}icon- Icon name (e.g.,"mdi:fire")icon_color- CSS color (e.g.,"#FF0000","red","rgb(255,0,0)")container_background_color- Container background color (e.g.,"#FF0000","red","rgba(255,0,0,0.5)")
icon- Icon nameicon_color- Icon colorcontainer_background_color- Entity container background color
content- Text contentcolor- Text color
value- Bar value (number or string)color- Bar colorlabel- Bar label text
{% set level = state | int %}
{
"icon": "mdi:battery-{{ (level / 10) | round(0) * 10 }}",
"icon_color": "{% if level <= 20 %}#FF0000{% elif level <= 50 %}#FF8800{% else %}#00CC00{% endif %}"
}Use this template with:
sensor.phone_batterysensor.laptop_batterysensor.watch_battery- ANY battery sensor!
{% set temp = state | float %}
{
"icon": "{% if temp > 25 %}mdi:fire{% elif temp < 15 %}mdi:snowflake{% else %}mdi:thermometer{% endif %}",
"icon_color": "{% if temp > 30 %}#FF0000{% elif temp > 25 %}#FF6600{% elif temp < 10 %}#0066FF{% elif temp < 15 %}#4444FF{% else %}#00CC00{% endif %}"
}{
"icon": "{% if state == 'on' %}mdi:lightbulb-on{% else %}mdi:lightbulb-off{% endif %}",
"icon_color": "{% if state == 'on' and attributes.rgb_color %}rgb({{ attributes.rgb_color | join(',') }}){% else %}#888888{% endif %}"
}{
"icon": "{% if state == 'home' %}mdi:home-account{% else %}mdi:car{% endif %}",
"icon_color": "{% if state == 'home' %}#4CAF50{% else %}#FF9800{% endif %}"
}{% set condition = state %}
{% set temp = attributes.temperature %}
{
"icon": "mdi:weather-{{ condition }}",
"icon_color": "{% if condition == 'sunny' %}#FFD700{% elif condition in ['rainy', 'pouring'] %}#4682B4{% elif condition == 'cloudy' %}#A9A9A9{% else %}var(--primary-color){% endif %}"
}{% if device_class == 'motion' %}
{
"icon": "{% if state == 'on' %}mdi:motion-sensor{% else %}mdi:motion-sensor-off{% endif %}",
"icon_color": "{% if state == 'on' %}#FF0000{% else %}#888888{% endif %}"
}
{% endif %}{% set level = state | int %}
{
"icon": "mdi:battery-{{ (level / 10) | round(0) * 10 }}",
"icon_color": "{% if level <= 20 %}#FF0000{% elif level <= 50 %}#FF8800{% else %}#00CC00{% endif %}",
"container_background_color": "{% if level <= 20 %}rgba(255,0,0,0.2){% elif level <= 50 %}rgba(255,136,0,0.15){% else %}transparent{% endif %}"
}This example shows how to highlight items needing attention - low battery items get a red background tint, medium battery gets orange, and good battery has no background.
By default, templates control display only (icon, color) and NOT the active/inactive state used for animations.
entity: sensor.temperature
active_state: "hot" # Controls when animations play
inactive_state: "cold"
unified_template: |
{ "icon": "mdi:fire", "icon_color": "red" }✅ Template changes icon and color
✅ Animations controlled by active_state and inactive_state
✅ Clear separation of concerns
Enable "Ignore Entity State Config" toggle to let template control state logic:
entity: sensor.temperature
ignore_entity_state_config: true # Template controls state too
unified_template: |
{% if state|int > 25 %}
{ "icon": "mdi:fire", "icon_color": "red" }
{% else %}
{ "icon": "mdi:snowflake", "icon_color": "blue" }
{% endif %}active_state and inactive_state are ignored
If you have existing legacy templates, Ultra Card will show a migration banner:
Before (Multiple Template Boxes):
dynamic_icon_template_mode: true
dynamic_icon_template: "{% if state|int > 25 %}mdi:fire{% else %}mdi:snowflake{% endif %}"
dynamic_color_template_mode: true
dynamic_color_template: "{% if state|int > 25 %}red{% else %}blue{% endif %}"After (One Unified Template):
unified_template_mode: true
ignore_entity_state_config: false # Preserves display-only behavior
unified_template: |
{
"icon": {% if state|int > 25 %}mdi:fire{% else %}mdi:snowflake{% endif %},
"icon_color": {% if state|int > 25 %}red{% else %}blue{% endif %}
}- From
template_mode→ignore_entity_state_config: true(preserves state control) - From
dynamic_*_template→ignore_entity_state_config: false(preserves display-only) - Legacy templates still work - Migration is optional
❌ Don't hardcode:
{% if states('sensor.living_room_temperature') | float > 25 %}✅ Use context:
{% if state | float > 25 %}❌ Don't use multiple templates:
- Dynamic Icon Template:
mdi:fire - Dynamic Color Template:
red
✅ Use one unified template:
{ "icon": "mdi:fire", "icon_color": "red" }✅ Check for valid data:
{% if state and state != 'unavailable' and state != 'unknown' %}
{% set level = state | int %}
{ "icon": "mdi:battery-{{ level }}", "icon_color": "green" }
{% else %}
{ "icon": "mdi:battery-unknown", "icon_color": "#888888" }
{% endif %}✅ Use theme colors:
{
"icon_color": "var(--primary-color)"
}✅ Generic battery template works everywhere:
{% set level = state | int %}
{
"icon": "mdi:battery-{{ (level / 10) | round(0) * 10 }}",
"icon_color": "{% if level <= 20 %}#FF0000{% elif level <= 50 %}#FF8800{% else %}#00CC00{% endif %}"
}Just change the entity - template adapts automatically!
Problem: Template shows blank icon
Solution: Check your Jinja2 syntax:
{% if state | int > 25 %} # ✅ Correct
{% if state > 25 %} # ❌ Wrong (state is string)Problem: "Invalid JSON" error
Solution: Ensure proper JSON syntax:
# ❌ Wrong - missing quotes
{ icon: mdi:fire }
# ✅ Correct - quoted property names and values
{ "icon": "mdi:fire" }Problem: state variable shows as undefined
Solution: Entity must exist in Home Assistant:
# Check entity exists first
{% if entity and state %}
...
{% endif %}Problem: Icon doesn't animate even though template shows fire icon
Solution: Check "Ignore Entity State Config" setting:
- If unchecked: Set
active_stateproperly - If checked: Template must return boolean-like value when state control is needed
{% if domain == 'light' %}
{
"icon": "{% if state == 'on' %}mdi:lightbulb-on{% else %}mdi:lightbulb-off{% endif %}",
"icon_color": "{% if state == 'on' and attributes.rgb_color %}rgb({{ attributes.rgb_color | join(',') }}){% else %}#888888{% endif %}"
}
{% elif domain == 'switch' %}
{
"icon": "{% if state == 'on' %}mdi:toggle-switch{% else %}mdi:toggle-switch-off{% endif %}",
"icon_color": "{% if state == 'on' %}#4CAF50{% else %}#888888{% endif %}"
}
{% endif %}{% set battery = state | int %}
{% set charging = attributes.is_charging %}
{
"icon": "{% if charging %}mdi:battery-charging-{{ (battery / 10) | round(0) * 10 }}{% else %}mdi:battery-{{ (battery / 10) | round(0) * 10 }}{% endif %}",
"icon_color": "{% if charging %}#00CC00{% elif battery <= 20 %}#FF0000{% elif battery <= 50 %}#FF8800{% else %}#00CC00{% endif %}"
}{% set hour = now().hour %}
{
"icon": "mdi:{% if hour < 6 %}weather-night{% elif hour < 12 %}weather-sunny{% elif hour < 18 %}weather-sunset{% else %}weather-night{% endif %}",
"icon_color": "{% if hour < 6 %}#4444AA{% elif hour < 12 %}#FFD700{% elif hour < 18 %}#FF8C00{% else %}#4444AA{% endif %}"
}{% set level = state | int %}
{
"icon": "mdi:battery-{{ (level / 10) | round(0) * 10 }}",
"icon_color": "{% if level <= 10 %}#FF0000{% elif level <= 20 %}#FF4400{% elif level <= 30 %}#FF8800{% elif level <= 50 %}#FFCC00{% elif level <= 70 %}#CCFF00{% elif level <= 90 %}#88FF00{% else %}#00CC00{% endif %}"
}{% set temp = state | float %}
{
"icon": "{% if temp > 30 %}mdi:fire{% elif temp > 25 %}mdi:thermometer-high{% elif temp > 15 %}mdi:thermometer{% elif temp > 10 %}mdi:thermometer-low{% else %}mdi:snowflake{% endif %}",
"icon_color": "{% if temp > 30 %}#FF0000{% elif temp > 25 %}#FF6600{% elif temp < 10 %}#0066FF{% elif temp < 15 %}#4444FF{% else %}#00CC00{% endif %}"
}{
"icon": "{% if state == 'on' %}mdi:lightbulb-on{% else %}mdi:lightbulb-off{% endif %}",
"icon_color": "{% if state == 'on' %}{% if attributes.rgb_color %}rgb({{ attributes.rgb_color | join(',') }}){% else %}#FFD700{% endif %}{% else %}#888888{% endif %}"
}{
"icon": "{% if state == 'on' %}mdi:motion-sensor{% else %}mdi:motion-sensor-off{% endif %}",
"icon_color": "{% if state == 'on' %}#FF4444{% else %}#888888{% endif %}"
}{
"icon": "{% if device_class == 'door' %}{% if state == 'on' %}mdi:door-open{% else %}mdi:door-closed{% endif %}{% elif device_class == 'window' %}{% if state == 'on' %}mdi:window-open{% else %}mdi:window-closed{% endif %}{% else %}{% if state == 'on' %}mdi:checkbox-marked-circle{% else %}mdi:checkbox-blank-circle-outline{% endif %}{% endif %}",
"icon_color": "{% if state == 'on' %}#FF9800{% else %}#4CAF50{% endif %}"
}Open your icon/info module in the editor. If you see a migration banner, you have legacy templates.
Note what your current templates do:
- Advanced Template Mode (
template_mode) - controls display AND state - Dynamic Icon Template - controls icon only
- Dynamic Color Template - controls color only
Ultra Card will automatically:
- Combine your templates into one
- Set
ignore_entity_state_configappropriately - Disable legacy templates
- Preserve exact behavior
- Check preview still looks correct
- Test animations still work as expected
- Verify entity remapping works
Now that you have entity context variables, update template to use them:
Before migration:
{% if states('sensor.phone_battery') | int <= 20 %}red{% else %}green{% endif %}After migration:
{% if state | int <= 20 %}red{% else %}green{% endif %}Now works with ANY battery entity!
- Use Simple Conditions - Avoid complex nested logic
- Cache Calculations - Use
{% set %}to store computed values - Minimal JSON - Only include properties you're changing
- Avoid External API Calls - Stick to Home Assistant data
A: Yes! Legacy templates continue to work. Migration is optional.
A: Unified template takes priority. Legacy templates are ignored.
A: By default, NO. Enable "Ignore Entity State Config" to let template control active/inactive state.
A: YES! That's the main benefit. Templates use context variables, so they work with any entity.
A: Yes! Only include properties you want to change:
{ "icon_color": "red" } # Only updates color, icon stays defaultA: Ultra Card shows a helpful error message in the editor and falls back to default values.
A: Yes! All standard Jinja2 and HA template functions work: states(), state_attr(), now(), distance(), etc.
For questions and examples, see:
- v2.1.0 - Initial unified template system release
- Icon and Info modules supported
- Auto-migration from legacy templates
- Entity context variables