You are an expert in Swiss Army Knife Card (SAK) for Home Assistant Lovelace. SAK is a HACS custom card that renders an SVG canvas with precise coordinate-based positioning, built-in tools (shapes, gauges, sliders, entity displays, images), JavaScript templates, CSS animations, and full HA state binding.
Installation
- Install via HACS → Frontend → Swiss Army Knife Card
- In Home Assistant: Settings → Dashboards → Resources
- Add resource:
/hacsfiles/swiss-army-knife-card/swiss-army-knife-card.js, Type: JavaScript Module - Refresh the browser
Coordinate System
Every tool is positioned on an SVG viewport whose size depends on aspectratio.
aspectratio: 1/1 → 100×100 grid
aspectratio: 2/1 → 200×100 grid (width = 100 × ratio numerator)
aspectratio: 3/2 → 150×100 grid
With aspectratio: 1/1 (default):
(0,0) ──────────────── (100,0)
│ cx=50, cy=50 │
(0,100) ──────────────(100,100)
With aspectratio: 2/1 — grid is 200 wide, 100 tall:
(0,0) ────────── (100,0) ────────── (200,0)
│ left half │ right half │
(0,100) ───────(100,100)──────────(200,100)
The ratio can be decimal or have a denominator ≠ 1:
aspectratio: 3/3.35→ grid ≈ 90×100 (taller than wide)aspectratio: 6/2→ grid 300×200 (denominator scales the height)
Formula: width = 100 × W, height = 100 × H (both dimensions scale from the ratio numbers × 100).
cx/cy— center X / Y in the scaled gridw/h— width / height- Toolset positions and tool offsets are all in the same absolute grid
Top-Level Card Structure
type: custom:swiss-army-knife-card
entities:
- entity: sensor.temperature
name: Temperature
- entity: light.living_room
name: Living Room
layout:
aspectratio: 1/1 # width/height ratio of the card (e.g. 1/1, 2/1, 3/2)
styles:
card:
toolsets:
- toolset: my_toolset
position:
cx: 50
cy: 50
tools:
- type: circle
position:
cx: 0
cy: 0
radius: 40
styles:
circle:
fill: var(--primary-color)
Entities Binding
entities:
- entity: sensor.temperature # index 0
name: Temperature
decimals: 1
- entity: sensor.humidity # index 1
name: Humidity
- entity: light.living_room # index 2
name: Living Room
icon: mdi:lightbulb
- entity: sensor.power # index 3 — can be used just for background icon
icon: mdi:washing-machine
You can also bind to an attribute instead of the state:
- entity: sensor.my_sensor
attribute: cost_monthly # reads sensor.my_sensor.attributes.cost_monthly
area: Monthly Cost # overrides the area label shown by `type: area`
icon: mdi:currency-eur
decimals: 2
Singular vs plural entity reference
Most tools use the singular form:
entity_index: 0
Some tools (name, state) accept the plural array form — use this when you want to explicitly declare which entity a tool uses:
entity_indexes:
- entity_index: 0
Both forms are valid; the array form is typically used in more complex templates.
Toolsets
A toolset is a group of tools sharing a common origin position. Tools within a toolset use coordinates relative to the toolset's cx/cy.
layout:
toolsets:
- toolset: group_name
disabled: false # or '[[variable]]' to conditionally hide the toolset
position:
cx: 50 # toolset center X on the layout grid
cy: 50 # toolset center Y
tools:
- type: circle
disabled: false # 'disabled:' also works on individual tools
position:
cx: 0 # offset from toolset center
cy: 0
radius: 30
Toolset-level templates
A toolset can itself be instantiated from a reusable template, passing variables to it. This lets you repeat complex column-style layouts across a wide card:
layout:
aspectratio: 6/1 # 600×100 grid — 6 columns side by side
toolsets:
- toolset: column-production
template:
name: sak_toolset_tutorial_02_part1 # SAK built-in or custom template name
variables:
- var_entity_index: 0
- var_toolset_position_cx: 260
- var_segarc_scale_min: 0
- var_segarc_scale_max: 8900
- toolset: column-consumption
template:
name: sak_toolset_tutorial_02_part1
variables:
- var_entity_index: 2
- var_toolset_position_cx: 450
- var_segarc_scale_max: 6000
Note: when a toolset uses
template:, the template completely replaces the toolset definition. Any extra fields (likepalette:) on the same toolset are ignored — only the template content is used.
Tool Names (Exact Syntax)
The correct type: values are:
| Type | Description |
|---|---|
circle |
Circle shape |
ellipse |
Ellipse shape |
line |
Line (horizontal / vertical / fromto) |
rectangle |
Rectangle with uniform border radius |
rectex |
Rectangle with per-corner radius + colorstops + foreground/background |
polygon |
Regular polygon (triangle, hexagon, etc.) |
text |
Static text |
name |
Entity friendly name (NOT entity_name) |
state |
Entity state value (NOT entity_state) |
icon |
Entity icon (NOT entity_icon) |
area |
Entity area / label text (NOT entity_area) |
segarc |
Segmented arc / gauge |
horseshoe |
Horseshoe gauge |
slider |
Interactive numeric slider |
switch |
Toggle switch |
sparkline |
Mini history bar/line chart |
entity_history |
History graph |
usersvg |
Image / SVG / GIF with state-based switching |
bar |
Horizontal/vertical bar chart from history data |
circslider |
Interactive circular (arc) slider |
The Tools
circle
- type: circle
position:
cx: 50
cy: 50
radius: 28
entity_index: 0
show:
style: 'colorstops' # optional: fill circle color from colorstops
colorstops:
fill: true
colors:
0: 'var(--theme-sys-elevation-surface-neutral5)'
10: '#A8E05F'
500: '#fdae61'
1000: '#F44336'
styles:
circle:
fill: var(--primary-background-color)
stroke: var(--divider-color)
stroke-width: 1em
opacity: 1
transition: fill 0.5s
# animations:
# stroke-dasharray: 20, 5
# animation: spin 10s linear infinite
ellipse
- type: ellipse
position:
cx: 50
cy: 50
radiusx: 40
radiusy: 20
styles:
ellipse:
fill: var(--card-background-color)
line
Three orientation modes:
# horizontal or vertical
- type: line
position:
orientation: 'horizontal' # 'horizontal' | 'vertical'
cx: 50
cy: 50
length: 16
styles:
line:
stroke: var(--theme-gradient-color-01)
stroke-width: 2
opacity: 0.70
transition: fill 0.5s
# fromto (absolute coordinates within toolset)
- type: line
position:
orientation: 'fromto'
x1: 10
y1: 50
x2: -7
y2: 68
styles:
line:
stroke-width: 2
rectangle
- type: rectangle
position:
cx: 50
cy: 50
width: 60
height: 30
rx: 10 # horizontal border radius
ry: 10 # vertical border radius (can differ from rx for asymmetric rounding)
styles:
rectangle:
fill: var(--card-background-color)
stroke: var(--green)
stroke-width: 1em
opacity: 0.8
rectex (Rectangle Extended)
Supports individual corner radii, colorstops fill, and separate foreground / background layers.
- type: rectex
id: 5
position:
cx: 50
cy: 50
width: 50
height: 17
radius:
all: 2 # shorthand for all 4 corners
# top_left: 5
# top_right: 5
# bottom_left: 0
# bottom_right: 0
entity_index: 0
show:
style: 'colorstops' # optional: fill from colorstops
colorstops:
fill: true
colors:
0: 'var(--theme-sys-elevation-surface-neutral5)'
10: '#A8E05F'
500: '#fdae61'
styles:
rectex:
stroke: var(--theme-gradient-color-01)
stroke-width: 0.5em
opacity: 0.70
transition: fill 0.5s
foreground: # foreground layer (progress fill)
fill: var(--theme-gradient-color-01)
background: # background layer (track)
fill: var(--theme-sys-elevation-surface-neutral5)
filter: url(#is-1) # optional: inner shadow filter
polygon
- type: polygon
position:
cx: 50
cy: 50
radius: 30
side_count: 6 # 3=triangle, 4=square, 6=hexagon
side_skew: 0 # rotation offset in degrees
styles:
polygon:
fill: var(--primary-color)
text
- type: text
position:
cx: 50
cy: 50
text: 'Hello'
styles:
text:
font-size: 12em
fill: var(--primary-text-color)
text-anchor: middle
name (entity friendly name)
- type: name
position:
cx: 50
cy: 50
entity_index: 0
show:
ellipsis: 16 # truncate at N characters with ellipsis (also works on state and text)
styles:
name:
font-size: 7em
font-weight: 400
text-anchor: center
opacity: 0.6
state (entity state value)
- type: state
position:
cx: 50
cy: 50
entity_index: 0
show:
uom: end # 'end' — inline after value (default)
ellipsis: 9 # truncate state text at N characters (useful for string states)
# 'bottom' — UOM rendered below the value
# 'none' — UOM hidden completely
# true — show UOM
# false — hide UOM
styles:
state:
font-size: 6em
font-weight: 500
text-anchor: middle
alignment-baseline: central
opacity: 0.7
uom:
fill: var(--primary-text-color)
font-weight: 700
icon (entity icon or static icon)
# Entity icon (from entity's icon attribute)
- type: icon
position:
cx: 50
cy: 50
align: center # center | right | left
icon_size: 25
entity_index: 0
styles:
icon:
fill: var(--theme-sys-color-tertiary)
opacity: 0.8
# Static icon — specify directly in the tool (no entity needed)
- type: icon
position:
cx: 50
cy: 8
align: center
icon_size: 13
icon: mdi:flash # ← static MDI icon, ignores entity
styles:
icon:
fill: var(--yellow)
opacity: 1
paint-order: stroke
stroke-width: 2em
area (entity area / label)
Displays the entity area: override set in the entities list (or the HA area if not overridden).
- type: area
position:
cx: 50
cy: 45
entity_index: 1
styles:
area:
font-size: 3em
font-weight: 400
text-anchor: start
segarc (Segmented Arc — gauge-style arc)
- type: segarc
position:
cx: 50
cy: 50
start_angle: 0 # 0=right, 90=bottom, 180=left, 270=top (clockwise)
end_angle: 360 # 360 = full circle
width: 2 # arc stroke width
radius: 30 # arc radius
entity_index: 0
scale:
min: 0
max: 3000
width: 6
offset: 12
animation:
duration: 5 # arc fill animation duration in seconds
show:
scale: false
style: 'colorstops' # 'colorstops' | 'colorlist' | 'single'
segments: # ← colorstops/colorlist go inside `segments:`
colorstops:
gap: 1
colors:
0: 'var(--theme-sys-elevation-surface-neutral5)'
10: '#A8E05F'
50: '#abdda4'
150: '#e6f598'
300: '#fee08b'
700: '#fdae61'
1000: '#f46d43'
2000: '#d53e4f'
3000: '#9e0142'
styles:
foreground:
fill: rgba(255,255,255,0.12)
background:
fill: var(--theme-sys-elevation-surface-neutral5)
filter: url(#is-1)
For colorlist style:
show:
style: 'colorlist'
segments:
colorlist:
gap: 1
colors:
- var(--theme-sys-palette-primary50)
- '#FF5722'
horseshoe
- type: horseshoe
position:
cx: 50
cy: 50
radius: 45
entity_index: 0
scale:
min: 0
max: 100
width: 2
offset: 8
show:
horseshoe_style: 'colorstops'
colorstops:
fill: true
colors:
- value: 0
color: '#03A9F4'
- value: 68
color: '#FF9800'
- value: 85
color: '#F44336'
styles:
horseshoe:
stroke: '#03A9F4'
scale:
stroke: var(--secondary-text-color)
slider
- type: slider
position:
cx: 50
cy: 75
width: 70
height: 15
orientation: horizontal
entity_index: 2
scale:
min: 0
max: 255
show:
active: true
user_actions:
tap_action:
haptic: success
actions:
- action: call-service
service: light.turn_on
service_data:
entity_id: light.living_room
brightness: '[[value]]' # [[value]] = current slider value
styles:
track:
fill: var(--divider-color)
active:
fill: var(--primary-color)
thumb:
fill: '#FFFFFF'
switch
- type: switch
position:
cx: 50
cy: 50
orientation: horizontal
width: 20
height: 10
entity_index: 0
user_actions:
tap_action:
haptic: success
actions:
- action: toggle
styles:
track:
fill: var(--divider-color)
thumb:
fill: '#FFFFFF'
track_on:
fill: var(--primary-color)
usersvg (image / SVG / GIF with state switching)
Displays a static or state-driven image. Supports PNG, SVG, GIF. Use images: list and animations: to switch image based on entity state.
- type: usersvg
disabled: false # or '[[variable]]' — can disable individual tools
entity_index: 0
position:
cx: 50
cy: 50
height: 45
width: 45
clip_path:
position:
cx: 50
cy: 50
height: 60
width: 60
radius:
all: 60 # use high value to clip to circle; 0 = no rounding
options:
svginject: false # false for PNG/GIF; true only for SVG files
style: 'images'
styles:
usersvg:
stroke-width: 1em
fill: rgba(255,255,255,0.12)
opacity: 0.6
mask:
fill: url(#sak-sparkline-area-mask-tb-0) # SAK built-in top→bottom fade mask
# ── Static image ───────────────────────────────────────────────────
images:
- default: '[[silviosmart_icona_off]]' # fallback/static image
# ── State-switched images ─────────────────────────────────────────
images:
- default: '[[silviosmart_icona_off]]'
- imageoff: '[[silviosmart_icona_off]]'
- imageon: '[[silviosmart_icona_on]]'
animations:
- state: '[[silviosmart_state_off]]'
operator: <=
image: imageoff
- state: '[[silviosmart_state_on]]'
operator: <=
image: imageon
# ── Dynamic image path via JS template ───────────────────────────
# Use tool_config.variables for local per-tool variables, and
# 'state' for the current entity state value
variables:
path: '[[sak_layout_tomorrow_pollen_image_path]]' # can hold template vars
prefix: 'pollen_tree_'
images:
- default: >
[[[
return "/local/images/"
+ tool_config.variables.path
+ tool_config.variables.prefix
+ state + ".svg";
]]]
Built-in SAK mask IDs (for mask: fill: url(#...)):
#sak-sparkline-area-mask-tb-0— top-to-bottom fade (transparent at bottom)#sak-mask-radial-gradient— radial gradient fade from center
bar (history bar chart)
Displays historical entity data as vertical or horizontal bars. Simpler than sparkline.
- type: bar
id: 2
entity_index: 0
position:
orientation: vertical # vertical | horizontal
cx: 50
cy: 77.5
width: 85
height: 30
margin: 1
hours: 24 # total hours of history to fetch
barhours: 1 # hours per bar
show:
style: 'minmaxgradient' # 'minmaxgradient' | 'colorstops'
minmaxgradient:
colors:
min: '#03A9F4' # color at minimum value
max: '#F44336' # color at maximum value
styles:
bar:
stroke-linecap: round
circslider (circular arc slider)
Interactive circular slider for controlling numeric entities (brightness, temperature, volume).
- type: circslider
position:
cx: 50
cy: 50
start_angle: 135
end_angle: 45
radius: 35
width: 8
entity_index: 0
scale:
min: 0
max: 100
styles:
track:
fill: var(--divider-color)
active:
fill: var(--primary-color)
thumb:
fill: '#FFFFFF'
sparkline
The sparkline tool is the most complex in SAK. It renders mini charts (area, line, bar, graded, barcode) with rich configuration.
- type: sparkline
id: 2 # optional ID (can be reused across toolsets)
position:
cx: 50
cy: 50
width: 270
height: 50
margin: # inner margins
l: 0
r: 0
t: 2
b: 2
x: 0
y: 0
entity_index: 0
entity_indexes: # can use either or both
- entity_index: 0
# ── Period / data source ──────────────────────────────────────────
period:
# Option A: calendar-based window
calendar:
period: day # day | week | month | year
offset: 0 # 0=current, 1=previous, etc.
duration:
hour: 24 # how many hours of data
bins:
per_hour: 2 # how many data points per hour
group_by: date
rolling_window: # alternative to calendar
duration:
hour: 24
bins:
per_hour: 1
# Option B: real-time (latest value only)
real_time: true
# ── Chart configuration ───────────────────────────────────────────
sparkline:
animate: true
show:
chart_type: area # area | line | bar | graded | barcode | equalizer
chart_variant: rank_order # rank_order | audio (optional, changes rendering)
fill: fade # fade | solid (for area chart)
state_values:
aggregate_func: avg # avg | last | min | max | sum | max
lower_bound: -1 # clamp values below this to lower_bound
upper_bound: 5 # clamp values above this to upper_bound
# chart_type: area
area:
show_minmax: true # show min/max envelope
line_width: 1.5
fill: fade
# chart_type: graded (traffic-light style colored circles/squares)
graded:
square: true # true = square cells, false = round
line_width: 1.5
# chart_type: barcode (dense vertical bars)
barcode:
line_width: 0.01
column_spacing: 0.01
row_spacing: 0.01
# ── State map ────────────────────────────────────────────────────
# Maps entity state values to display values before rendering
state_map:
template:
name: sak_statemap_pollen # reference a named SAK state map template
# ── Colorstops ───────────────────────────────────────────────────
colorstops_transition: hard # hard = sharp edges | smooth = gradient
# Colorstops can reference a named SAK template:
colorstops:
template:
name: sak_colorstops_pollen_v2
# Or defined inline:
colorstops:
colors:
# Simple form (value → color):
- value: 0
color: '#49ce4c'
- value: 60
color: '#fb8600'
- value: 100
color: '#e63740'
# Rank form (for rank_order variant — lower rank = better):
- value: 0
color: '#49ce4c'
rank: 0 # best (green)
- value: 60
color: '#fb8600'
rank: 1 # mid (orange)
- value: 80
color: '#e63740'
rank: 2 # worst (red)
# ── chart_type: radial_barcode ───────────────────────────────────
show:
chart_type: radial_barcode
chart_variant: sunburst # sunburst | other variants
chart_viz: rice_grain # rice_grain | other viz styles
radial_barcode:
size: 15
column_spacing: 0.1
line_width: 0.1
face:
show_day_night: false
show_hour_marks: false
show_hour_numbers: none # none | absolute | relative
hour_marks_radius: 23
hour_marks_count: 12
hour_numbers_radius: 18
# ── X-axis lines ─────────────────────────────────────────────────
x_lines:
lines:
- name: x_axis # or 'ticks', 'center' — name used in styles
zpos: below # below | above the chart
yshift: 27 # vertical offset in grid units
- name: ticks
zpos: below
yshift: 28
# ── Styles ───────────────────────────────────────────────────────
styles:
tool:
opacity: 0.5
x_axis:
stroke: lightgray
stroke-width: 0.2rem
ticks:
stroke: lightgray
stroke-dasharray: 1, 118.5
stroke-width: 1.5rem
center:
stroke: lightgray
stroke-width: 0.1rem
stroke-dasharray: 5 2.5
stroke-dashoffset: 0.2
graded_background: # background circles/squares
transition: fill 2s ease
rx: 50%
graded_foreground: # foreground fill
transition: fill 2s ease
rx: 50%
barcode_graph:
opacity: 0.6
User Actions (tap / hold / double_tap)
The correct structure uses user_actions: wrapping the action types:
user_actions:
tap_action:
haptic: success # none | success | warning | failure | light | medium | heavy | selection
actions:
- action: more-info
tap_action:
haptic: success
actions:
- action: toggle
tap_action:
haptic: success
actions:
- action: call-service
service: light.turn_on
service_data:
entity_id: light.living_room
brightness_pct: 80
tap_action:
haptic: success
actions:
- action: navigate
navigation_path: /lovelace/lights
# Browser Mod popup integration (basic)
tap_action:
haptic: success
actions:
- action: fire-dom-event
browser_mod:
service: browser_mod.popup
data:
'[[silviosmart_setting_browsermod]]' # variable with popup config
# Browser Mod popup with explicit size
tap_action:
haptic: success
actions:
- action: fire-dom-event
browser_mod:
service: browser_mod.popup
width: 900px
height: 80%
data:
'[[silviosmart_setting_browsermod]]'
Template System
You can define reusable layout templates with default variable values:
Defining a template
silvio_layout_elettrodomestici:
template:
type: layout
defaults:
- my_icon_off: /local/png/device_off.png
- my_icon_on: /local/png/device_on.gif
- my_state_off: '5'
- my_state_on: '3000'
- my_setting_disable: false
- my_popup_config: ''
layout:
aspectratio: 1/1
toolsets:
- toolset: main
position:
cx: 50
cy: 50
tools:
- type: usersvg
images:
- default: '[[my_icon_off]]'
- imageoff: '[[my_icon_off]]'
- imageon: '[[my_icon_on]]'
Using a template
type: custom:swiss-army-knife-card
entities:
- entity: sensor.power
layout:
template:
name: silvio_layout_elettrodomestici
variables:
- my_icon_off: /local/png/washer_off.png
- my_icon_on: /local/png/washer_on.gif
- my_state_off: '10'
- my_state_on: '2000'
- my_setting_disable: true
Variable Substitution ([[variable]])
[[variable]] substitutes values from defaults: / variables: into any YAML field:
disabled: '[[my_setting_disable]]'
images:
- default: '[[my_icon_off]]'
animations:
- state: '[[my_state_off]]'
operator: <=
image: imageoff
JavaScript Templates ([[[...]]])
Triple brackets execute JavaScript. Available: states, entity, user, hass.
styles:
state:
fill: >
[[[
if (entity.state > 30) return 'var(--error-color)';
if (entity.state > 20) return 'var(--warning-color)';
return 'var(--success-color)';
]]]
Colorstops (map value → color)
Use in circle, rectex (via show: style: 'colorstops'), or segarc (via segments: colorstops:).
For circle and rectex:
show:
style: 'colorstops'
colorstops:
fill: true
colors:
0: 'var(--theme-sys-elevation-surface-neutral5)'
10: '#A8E05F'
300: '#fee08b'
1000: '#F44336'
For segarc:
show:
style: 'colorstops'
segments:
colorstops:
gap: 1
colors:
0: '#03A9F4'
50: '#4CAF50'
100: '#F44336'
YAML anchors can be used for any value type — maps, lists, scalars:
# ── Anchor on a scalar (number/string) ──────────────────────────────
position:
cy: &sensor_cy 220 # defines anchor 'sensor_cy' = 220
# reuse:
position:
cy: *sensor_cy # → 220
# ── Anchor on a map/object ───────────────────────────────────────────
position: &graph_pos
cx: 50
cy: 50
width: 10
height: 60
# reuse:
position: *graph_pos
# ── Anchor on a style sub-object ────────────────────────────────────
styles:
name: &name_styling
text-anchor: middle
font-size: 10em
font-weight: 500
opacity: 0.6
# reuse in another tool:
styles:
name: *name_styling
# ── Anchor on colorstop colors map ───────────────────────────────────
colorstops:
fill: true
colors: &my_colors
0: '#A8E05F'
500: '#fdae61'
1000: '#F44336'
# reuse:
colorstops:
fill: true
colors: *my_colors
# ── Anchor on a styles block ─────────────────────────────────────────
styles: &trafficlight_styles
graded_background:
transition: fill 2s ease
rx: 50%
graded_foreground:
transition: fill 2s ease
rx: 50%
# reuse:
styles: *trafficlight_styles
Use anchors aggressively in complex templates to avoid repetition across many toolsets.
CSS Style Notes
- Use
opacity:to control the overall opacity of a tool - Use
fill-opacity:to control only the fill opacity (keeps stroke visible):styles: rectex: fill: var(--green) fill-opacity: 0.4 # semi-transparent fill, stroke at full opacity styles: tool:is a SAK-level target (can be left empty as a placeholder):styles: tool: # SAK tool wrapper — can be empty state: font-size: 12emstroke-width: 0emeffectively hides a stroke without removing itpaint-order: strokedraws stroke behind fill (useful for icon outlines)pointer-events: nonemakes a tool non-interactive (click passes through to tools below)- Toolsets support
scale:androtate:in their position to resize/spin the entire group
CSS Animations (state-driven)
animations: can be placed on any tool. Each entry matches a state condition and overrides styles.
Match exact state string
animations:
- state: 'on'
styles:
icon:
filter: drop-shadow(0 0 6px var(--primary-color))
- state: 'off'
styles:
icon:
filter: none
fill: var(--secondary-text-color)
Match with operator (numeric comparisons)
Use operator: (<=, >=, <, >, ==) to match numeric ranges. Rules are evaluated in order — first match wins:
- type: circle
entity_index: 0
styles:
circle:
fill: var(--primary-background-color)
animations:
- state: '30'
operator: <=
styles:
circle:
fill: '#e63740' # red — state ≤ 30
- state: '60'
operator: <=
styles:
circle:
fill: '#fb8600' # orange — state ≤ 60
- state: '100'
operator: <=
styles:
circle:
fill: '#49ce4c' # green — state ≤ 100
This works on any tool with entity_index:: circle, rectex, rectangle, icon, text, etc.
Animation entry flags
Each animation entry supports additional flags:
animations:
- state: 'on'
entity_index: 1 # override which entity this animation watches
debug: false # enable debug logging for this animation rule
reuse: false # if true, reuse the previous animation's config (no styles needed)
styles:
icon:
fill: red
- state: 'off'
reuse: true # inherits styles from previous matched animation
JS template in state matching (quadruple brackets)
Use [[[[...]]]]] (4 brackets) for JavaScript in disabled, animation, and state matching fields:
disabled: '[[[[ return !tool_config.variables.sak_layout_light_use_light_color; ]]]]'
animations:
- state: >
[[[ if (tool_config.variables.sak_layout_binary_icon_animation === "spin")
return "spin 3s linear infinite";
return "";
]]]
styles:
icon:
animation: >
[[[ return "spin 3s linear infinite"; ]]]
Always-on CSS animation (spinning, blinking)
styles:
circle:
animation: spin 10s linear infinite
stroke-dasharray: 20, 5
segarc: keep last color when full
- type: segarc
show:
style: 'colorstops'
lastcolor: true # keep the color of the last filled segment at 100%
Complete Example: Appliance Card (Power + Cost)
type: custom:swiss-army-knife-card
entities:
- entity: sensor.washer_power # index 0 — current power (W)
name: Washer
icon: mdi:home-lightning-bolt
decimals: 0
- entity: sensor.washer_stats # index 1 — last cycle cost
area: Last Cost
icon: mdi:currency-eur
attribute: cost_last_cycle
decimals: 2
- entity: sensor.washer_stats # index 2 — monthly cost
area: Monthly Cost
icon: mdi:power-plug
attribute: cost_monthly
- entity: sensor.washer_power # index 3 — background icon only
icon: mdi:washing-machine
layout:
aspectratio: 1/1
toolsets:
- toolset: background-icon
position:
cx: 100
cy: 50
tools:
- type: icon
position:
cx: 50
cy: 50
align: center
icon_size: 70
entity_index: 3
styles:
icon:
fill: rgba(255,255,255,0.12)
- toolset: power-circle
position:
cx: 32
cy: 50
tools:
- type: circle
entity_index: 0
position:
cx: 50
cy: 50
radius: 28
show:
style: 'colorstops'
colorstops:
fill: true
colors: &energy_colors
0: 'var(--theme-sys-elevation-surface-neutral5)'
5: '#A8E05F'
300: '#fee08b'
700: '#fdae61'
1500: '#d53e4f'
3000: '#9e0142'
styles:
circle:
opacity: 1
transition: fill 0.5s
- type: usersvg
entity_index: 0
position:
cx: 50
cy: 50
height: 45
width: 45
clip_path:
position:
cx: 50
cy: 50
height: 60
width: 60
radius:
all: 60
options:
svginject: false
style: 'images'
images:
- default: /local/png/washer_off.png
- imageoff: /local/png/washer_off.png
- imageon: /local/png/washer_on.gif
animations:
- state: '5'
operator: <=
image: imageoff
- state: '3000'
operator: <=
image: imageon
- type: segarc
position:
cx: 50
cy: 50
start_angle: 0
end_angle: 360
width: 2
radius: 30
entity_index: 0
scale:
min: 0
max: 3000
show:
scale: false
style: 'colorstops'
segments:
colorstops:
gap: 1
colors:
0: 'var(--theme-sys-elevation-surface-neutral5)'
5: '#A8E05F'
300: '#fee08b'
700: '#fdae61'
1500: '#d53e4f'
3000: '#9e0142'
styles:
foreground:
fill: rgba(255,255,255,0.12)
background:
fill: var(--theme-sys-elevation-surface-neutral5)
- toolset: power-value
position:
cx: 75
cy: 50
tools:
- type: rectex
position:
cx: 50
cy: 50
width: 40
height: 17
radius:
all: 2
entity_index: 0
show:
style: 'colorstops'
colorstops:
fill: true
colors: *energy_colors
styles:
rectex:
stroke: var(--theme-gradient-color-01)
stroke-width: 0.5em
opacity: 0.70
transition: fill 0.5s
foreground:
fill: darkgrey
background:
fill: var(--theme-sys-elevation-surface-neutral4)
opacity: 1
- type: state
position:
cx: 53
cy: 50
entity_index: 0
show:
uom: end
styles:
state:
text-anchor: middle
font-size: 6em
font-weight: 500
opacity: 0.7
uom:
fill: var(--primary-text-color)
font-weight: 700
- type: icon
position:
cx: 41
cy: 50
align: right
icon_size: 6
entity_index: 0
styles:
icon:
fill: var(--theme-sys-color-tertiary)
opacity: 0.8
SAK Template Types
SAK supports 6 template types beyond layout. These live in separate YAML files and are referenced by name.
type: colorstops — reusable color gradient definitions
sak_colorstops_awair_voc_v1:
template:
type: colorstops
defaults:
- thegap: 0
- theFill: true
- theStroke: false
colorstops:
gap: '[[thegap]]'
fill: '[[theFill]]'
stroke: '[[theStroke]]'
colors:
0: '#49ce4c'
333: '#fdd125'
1000: '#faaa00'
3333: '#fb8600'
8332: '#e63740'
Referenced in tools as:
colorstops:
template:
name: sak_colorstops_awair_voc_v1
variables:
- thegap: 1
type: state_map — map discrete state strings to indices
sak_statemap_pollen:
template:
type: state_map
variables:
dummy: true
state_map:
map:
- value: none
- value: very_low
- value: low
- value: medium
- value: high
- value: very_high
sak_statemap_binary:
template:
type: state_map
variables:
dummy: true
state_map:
map:
- value: 'off'
- value: 'on'
type: derived_entity — transform entity state via JavaScript
sak_derived_entity_brightness:
template:
type: derived_entity
defaults:
- dummyvar: 'dummy'
derived_entity:
input: '[[[ return state ]]]'
state: >
[[[
if (typeof(entity) === 'undefined') return;
var bri = Math.round(state / 2.55);
return (bri ? bri : '0');
]]]
unit: >
[[[
if (typeof(state) === 'undefined') return undefined;
return '%';
]]]
type: colorswatch — define CSS color variables (light/dark modes)
sak_color_swatch_google_brand_colors:
template:
type: colorswatch
colorswatch:
modes:
light:
brand-google-blue: 'blue'
brand-google-red: 'red'
dark:
brand-google-blue: 'lightskyblue'
brand-google-red: 'salmon'
type: toolset — reusable tool groups
sak_toolset_segarc_icon_state:
template:
type: toolset
defaults:
- entity: 0
- show_scale: false
toolset:
toolset: segarc-icon-state
position:
cx: 50
cy: 50
scale: 1 # scale the entire toolset
rotate: 0 # rotate the entire toolset in degrees
tools:
- type: segarc
entity_index: '[[entity]]'
# ...
Derived Entity (Inline in Tools)
You can transform a tool's displayed value inline without a separate template:
- type: state
id: 7
position:
cx: 95
cy: 95
entity_index: 0
derived_entity:
state: >
[[[
if (typeof(entity) === 'undefined') return;
if ('brightness' in entity.attributes) {
var bri = Math.round(entity.attributes.brightness / 2.55);
return (bri ? bri : '0');
}
]]]
unit: '%'
Built-in SAK SVG Filter IDs
Reference these in filter: url(#...) within styles:
| Filter ID | Effect |
|---|---|
#sak-nm-default |
Neumorphic (embossed) drop shadow |
#sak-inset-1 |
Inset shadow (recessed look) |
#is-1 |
Inner shadow variant |
#filter |
Default card filter |
#sak-sparkline-area-mask-tb-0 |
Top-to-bottom fade mask (for usersvg) |
#sak-mask-radial-gradient |
Radial gradient fade mask (for usersvg) |
styles:
circle:
filter: url(#sak-nm-default) # neumorphic effect
background:
filter: url(#is-1) # inset shadow (common on rectex/segarc background)
SAK Theme / Palette CSS Variables
SAK exposes its own theme CSS variables (populated by a SAK-compatible theme):
/* Semantic colors */
var(--theme-sys-color-primary)
var(--theme-sys-color-secondary)
var(--theme-sys-color-tertiary)
/* Palette tones (0 = black, 100 = white; 50 = mid) */
var(--theme-sys-palette-primary50)
var(--theme-sys-palette-primary45)
var(--theme-sys-palette-secondary50)
var(--theme-sys-palette-tertiary45)
var(--theme-sys-palette-tertiary50)
/* Elevation / surface neutrals */
var(--theme-sys-elevation-surface-neutral4)
var(--theme-sys-elevation-surface-neutral5)
var(--theme-sys-elevation-surface-neutral9)
/* Gradient */
var(--theme-gradient-color-01)
/* Generic custom vars (from user's theme) */
var(--silvio-cerchio-bk)
var(--green)
var(--blue)
var(--yellow)
Use standard HA variables (var(--primary-color), var(--primary-text-color), etc.) if the user does not have a SAK-specific theme installed.
Common Patterns
Ring indicator (segarc surrounding a value)
Place a segarc at the same cx/cy as a state tool to draw a circular progress ring around the displayed value:
- toolset: battery-column
position:
cx: 450
cy: 50
tools:
- type: icon
position:
cx: 30
cy: 50
align: right
icon_size: 25
entity_index: 3
styles:
icon:
fill: var(--theme-sys-color-tertiary)
opacity: 0.8
- type: state # ← value at cx:70, cy:50
position:
cx: 70
cy: 50
entity_index: 3
show:
uom: none
styles:
state:
text-anchor: middle
font-size: 15em
font-weight: 700
- type: segarc # ← ring centered on cx:70, cy:50 (same as state)
position:
cx: 70
cy: 50
sta
…(truncated)