一键导入
swiss-army-knife-card
en: "Expert assistant for Swiss Army Knife Card — pixel-precise SVG Lovelace cards with shapes, gauges, sliders and animated tools"
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
en: "Expert assistant for Swiss Army Knife Card — pixel-precise SVG Lovelace cards with shapes, gauges, sliders and animated tools"
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
en: "Expert assistant for HTML-JS Card — custom Home Assistant Lovelace cards with HTML, CSS and JavaScript"
Esperto di HTML-JS Card per Home Assistant Lovelace. Usa quando devi creare card con HTML, CSS e JavaScript inline: variabili hass/entities/card, hass-update event, callService, shadow DOM.
Esperto di Mushroom UI Cards per Home Assistant Lovelace. Usa quando devi creare o modificare card Mushroom: mushroom-template-card, chip rows, badge, layout strategies, tap actions, theming.
Esperto di Swiss Army Knife Card (SAK) per Home Assistant Lovelace. Usa quando devi creare, modificare o analizzare card SAK: tool types, coordinate system, colorstops, segarc, sparkline, templates, animazioni.
en: "Expert assistant for Mushroom UI Cards in Home Assistant Lovelace"
| name | swiss-army-knife-card |
| version | 1.6.0 |
| description | {"en":"Expert assistant for Swiss Army Knife Card — pixel-precise SVG Lovelace cards with shapes, gauges, sliders and animated tools","it":"Assistente esperto per Swiss Army Knife Card — card Lovelace SVG con forme, gauge, slider e tool animati a posizionamento preciso","es":"Asistente experto para Swiss Army Knife Card — tarjetas Lovelace SVG con formas, medidores, sliders y herramientas animadas","fr":"Assistant expert pour Swiss Army Knife Card — cartes Lovelace SVG avec formes, jauges, sliders et outils animés"} |
| author | Bobsilvio |
| tags | ["lovelace","cards","svg","shapes","gauge","slider","animation","dashboard","custom"] |
| min_version | 4.6.0 |
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.
/hacsfiles/swiss-army-knife-card/swiss-army-knife-card.js, Type: JavaScript ModuleEvery 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 / heighttype: 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:
- 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
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.
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
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.
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 |
- 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
- type: ellipse
position:
cx: 50
cy: 50
radiusx: 40
radiusy: 20
styles:
ellipse:
fill: var(--card-background-color)
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
- 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
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
- 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)
- type: text
position:
cx: 50
cy: 50
text: 'Hello'
styles:
text:
font-size: 12em
fill: var(--primary-text-color)
text-anchor: middle
- 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
- 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
# 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
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
- 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'
- 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)
- 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'
- 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)
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 centerDisplays 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
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'
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
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]]'
You can define reusable layout templates with default variable values:
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]]'
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]])[[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
[[[...]]])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)';
]]]
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.
opacity: to control the overall opacity of a toolfill-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: 12em
stroke-width: 0em effectively hides a stroke without removing itpaint-order: stroke draws stroke behind fill (useful for icon outlines)pointer-events: none makes a tool non-interactive (click passes through to tools below)scale: and rotate: in their position to resize/spin the entire groupanimations: can be placed on any tool. Each entry matches a state condition and overrides styles.
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)
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.
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
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"; ]]]
styles:
circle:
animation: spin 10s linear infinite
stroke-dasharray: 20, 5
- type: segarc
show:
style: 'colorstops'
lastcolor: true # keep the color of the last filled segment at 100%
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 supports 6 template types beyond layout. These live in separate YAML files and are referenced by name.
type: colorstops — reusable color gradient definitionssak_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 indicessak_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 JavaScriptsak_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 groupssak_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]]'
# ...
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: '%'
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 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.
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
start_angle: 0
end_angle: 360 # full circle
width: 3 # thin ring
radius: 18
entity_index: 3
scale:
min: 0
max: 100
show:
scale: false
style: 'colorlist'
segments:
colorlist:
gap: 1
colors:
- var(--theme-sys-palette-tertiary45)
styles:
foreground:
fill: darkgrey
background:
fill: var(--theme-sys-elevation-surface-neutral4)
opacity: 1
For aspectratio: 6/1 (600×100 grid), place toolsets at regular cx intervals with vertical lines as separators:
layout:
aspectratio: 6/1
toolsets:
- toolset: separator-1
position:
cx: 200
cy: 50
tools:
- type: line
position:
cx: 50
cy: 50
orientation: vertical
length: 50
styles:
line:
fill: var(--primary-text-color)
opacity: 0.5
- toolset: separator-2
position:
cx: 410
cy: 50
tools:
- type: line
# ...same as above
- toolset: half-circle-left
position:
cx: 0
cy: 50
tools:
- type: circle
position:
cx: 50
cy: 50
radius: 48
styles:
circle:
fill: none
stroke: var(--theme-sys-color-secondary)
stroke-width: 3em
opacity: 0.5
- toolset: col1 # cx ~ 25 or 120 for left area
position:
cx: 120
cy: 50
tools: [...]
- toolset: col2 # cx ~ 260 for first data column
position:
cx: 260
cy: 50
tools: [...]
- toolset: col3 # cx ~ 355 for second data column
position:
cx: 355
cy: 50
tools: [...]
- toolset: col4 # cx ~ 450 for third data column
position:
cx: 450
cy: 50
tools: [...]
- toolset: col5 # cx ~ 540 for fourth data column
position:
cx: 540
cy: 50
tools: [...]
type: custom:swiss-army-knife-card.aspectratio: height is always 100, width = 100 × (W/H). Examples: 1/1=100×100, 2/1=200×100, 3/2=150×100, 6/1=600×100.entities: array; reference them via entity_index: (0-based) or entity_indexes: [{entity_index: N}].state, name, icon, area — NOT entity_state, entity_name, entity_icon, entity_area.segments: colorstops: (not directly on the tool).user_actions: tap_action: haptic: ... actions: [...] structure.segarc or horseshoe for gauge-type displays; use state for plain text values.usersvg for PNG/GIF images, with images: list and animations: for state switching.type: layout and use defaults: / variables:.colorstops / colorlist over JS templates when possible.&name / *name) to reuse colorstop maps, style blocks, positions, and even scalar values (cy: &my_cy 50) across tools.icon: mdi:xxx directly in the tool (no entity_index).fill-opacity: (not opacity:) when you want a semi-transparent fill but a fully opaque stroke.var(--primary-color), etc.) for dark/light mode compatibility.