Skip to content

Shape Texture System

What It Is

The shape_texture feature renders an SVG-native texture or animation inside the button or elbow shape boundary. It is a hybrid approach that sits alongside the existing background_animation canvas system:

SystemRenderingScopeAnimation
background_animationCanvas 2DFull card bleedanime.js / RAF
shape_textureSVG-nativeInside shape fill onlyDeclarative SVG (<animateTransform>, <animate>)

The canvas system continues to handle full-bleed card backgrounds unchanged. shape_texture provides a declarative, zero-JS-overhead texture layer composited directly within the SVG shape fill.

Supported Card Types

  • Button card — preset mode only (no custom SVG, no component mode)
  • Elbow card — simple and segmented styles

The guard _supportsShapeTexture() on LCARdSButton returns true only when !this._processedSvg && !this.config?.component && !this.config?.svg.

Config Schema

yaml
type: custom:lcards-button
preset: lozenge
entity: light.bedroom
shape_texture:
  preset: grid           # See Preset Reference for all options
  opacity: 0.3           # 0–1, or state-based object
  speed: 1.0             # Global speed multiplier (0 = static), or state-based object
  mix_blend_mode: normal # CSS mix-blend-mode (normal | multiply | screen | overlay | …)
  config:                # Preset-specific parameters
    color: "rgba(255,255,255,0.4)"
    line_spacing: 40
    scroll_speed_x: 20
    scroll_speed_y: 0
    pattern: both        # grid only: both | horizontal | vertical

State-based opacity example

yaml
shape_texture:
  preset: pulse
  opacity:
    active: 0.8
    inactive: 0.15
    default: 0.35
  config:
    color: "var(--lcars-alert-red)"
    speed: 1.5

State-based speed example (animated → static on active)

yaml
shape_texture:
  preset: grid
  speed:
    default: 1.0
    active: 0.0    # freeze animation when active
  opacity: 0.25

State-based fill level example

yaml
shape_texture:
  preset: level
  config:
    color: "rgba(0,220,120,0.75)"
    fill_pct:
      default: 0
      template: "[[[return entity.attributes.battery_level ?? 0]]]"

Preset Reference

All 11 built-in presets are listed below. Color fields accept any of: rgba(), #hex, var(--css-variable), {theme:token.path}, or state-based maps — the color pipeline resolves all formats before they reach the SVG renderer (see Color Pipeline).

grid

Scrolling orthogonal grid lines.

Config keyDefaultDescription
colorrgba(255,255,255,0.3)Line color
line_width1Stroke width in px
line_spacing40Grid cell size in px
scroll_speed_x20Horizontal scroll speed px/s (negative = reverse)
scroll_speed_y0Vertical scroll speed px/s
pattern'both''both' | 'horizontal' | 'vertical'

diagonal

Scrolling diagonal hatching.

Config keyDefaultDescription
colorrgba(255,255,255,0.3)Line color
line_width1Stroke width in px
line_spacing40Tile size in px
scroll_speed_x20Horizontal scroll speed px/s
scroll_speed_y20Vertical scroll speed px/s

hexagonal

Scrolling hexagonal grid (pointy-top orientation).

Config keyDefaultDescription
colorrgba(255,255,255,0.3)Stroke color
line_width1Stroke width
hex_radius20Hex cell radius in px
scroll_speed_x15Horizontal scroll speed px/s
scroll_speed_y0Vertical scroll speed px/s

dots

Scrolling dot grid.

Config keyDefaultDescription
colorrgba(255,255,255,0.4)Dot fill color
dot_radius2Dot radius in px
spacing20Grid cell size in px
scroll_speed_x15Horizontal scroll speed px/s
scroll_speed_y0Vertical scroll speed px/s

fluid

Organic swirling noise effect. Large fractalNoise blobs drift diagonally while the noise continuously evolves via SMIL <animate baseFrequency>. The ±5 % baseFrequency variation is imperceptible as a size change but causes the blob patterns to genuinely morph over time — no scroll seams or repeating loops.

Config keyDefaultDescription
colorrgba(100,180,255,0.8)Blob color
base_frequency0.010Turbulence base frequency (lower = larger blobs)
num_octaves4Turbulence octaves (higher = more detail)
scroll_speed_x7Diagonal scroll x component px/s
scroll_speed_y10Diagonal scroll y component px/s

plasma

Dual-colour fractalNoise wash — two colours (color_a/color_b) are screen-blended using opposing turbulence channel masks. Creates a vivid energy/plasma look.

Config keyDefaultDescription
color_argba(80,0,255,0.9)First colour
color_brgba(255,40,120,0.9)Second colour
base_frequency0.012Turbulence base frequency
num_octaves2Turbulence octaves
scroll_speed_x8Horizontal scroll speed px/s
scroll_speed_y5Vertical scroll speed px/s

shimmer

A directional light-sweep gradient that continuously traverses the shape. The angle parameter controls sweep direction; the highlight sweeps from off-left to off-right over a 3× tile width to avoid visible wrap.

Config keyDefaultDescription
colorrgba(255,255,255,0.55)Highlight color
highlight_width0.35Highlight band width as a fraction of tile width (0–1)
speed2.5Sweep speed in px/s
angle30Sweep angle in degrees

flow

Directional streaming currents. Horizontally-elongated turbulence streaks (baseFrequency x:y ratio ~6:1) are warped by a second static displacement layer, then scrolled at high speed. The warp turbulence is deliberately static — no baseFrequency animation — so there are no visible jump discontinuities.

Config keyDefaultDescription
colorrgba(0,200,255,0.7)Streak color
base_frequency0.012Turbulence base frequency for streaks
wave_scale8Displacement map scale (warp amplitude)
scroll_speed_x50Horizontal scroll speed px/s (high = fast current)
scroll_speed_y0Vertical scroll speed px/s

level

Animated level-indicator fill bar that rises from the bottom (or fills from the left). The fill level is set via fill_pct (0–100), which can be a template so it tracks an entity attribute. An optional animating cubic-Bezier wave runs along the leading edge.

Config keyDefaultDescription
colorrgba(0,200,100,0.7)Fill color
fill_pct50Fill percentage 0–100; supports templates
direction'up''up' (bottom→top) | 'right' (left→right)
edge_glowtrueThin white highlight on leading edge
wave_height4Wave amplitude in px (0 = flat edge)
wave_speed20Wave scroll speed in px/s
wave_count4Number of wave crests across the shape width

direction: 'right' does not support waves — the leading edge is always flat.

pulse

Breathing radial glow for attention / alert indicators. A radialGradient ellipse expands and contracts; opacity also animates to create a punchy in/out effect.

Config keyDefaultDescription
colorrgba(255,80,0,0.8)Glow center color (fades to transparent at edge)
speed1.2Breathe cycles per second
radius0.7Maximum glow radius as a fraction of shape diagonal
min_size0.15Minimum glow size as a fraction of max radius

scanlines

Classic CRT-style scan-line overlay. Works as a darkening (or lightening) overlay on top of any shape fill colour.

Config keyDefaultDescription
colorrgba(0,0,0,0.25)Line color
line_spacing4Distance between lines in px
line_width1.5Line stroke width in px
direction'horizontal''horizontal' | 'vertical'
scroll_speed_y0Vertical scroll speed px/s (horizontal direction)
scroll_speed_x0Horizontal scroll speed px/s (vertical direction)

Architecture

SVG Layer Order

The texture layer is injected between backgroundMarkup and borderMarkup:

${backgroundMarkup}    ← shape fill (rect/path)
${textureMarkup}       ← <defs> + clipped texture rect/path
${borderMarkup}        ← borders
${iconData.markup}     ← icon
${textMarkup}          ← text

ID Scoping

All <defs> IDs include a per-instance unique suffix (e.g., stex-clip-abc12, stex-pattern-abc12) generated by _getTextureInstanceId(). This prevents collisions when multiple cards exist on the same dashboard.

Color Pipeline

Colors in shape_texture config travel through a four-stage resolution pipeline before reaching the SVG renderer:

User config value (JS template / token / theme token / CSS var / rgba / hex / state map)

    ▼  Stage 0 — Template evaluation (in _resolveShapeTextureConfig)
    │  Evaluates [[[JS]]] and {token} templates in all config string values
    │  fill_pct also supports { default: N, template: "[[[...]]]" } object form

    ▼  Stage 1 — resolveThemeTokensRecursive()   (in _resolveShapeTextureConfig)
    │  Resolves {theme:…} tokens → CSS variable or concrete value

    ▼  Stage 2 — ColorUtils.resolveCssVariable() (per color field, same function)
    │  Resolves var(--…) CSS variables → concrete color strings

    ▼  Stage 3 — createDefs(id, cfg, ctx)
       SVG feFlood flood-color="…" / fill="…" attribute
       Browser HTML parser resolves any remaining CSS natively

All turbulence and glow presets (fluid, plasma, flow) use feFlood flood-color + feComposite operator="in" rather than feColorMatrix RGB extraction. This means any color format the SVG parser understands (including CSS custom properties that have been resolved to a concrete value by the time they reach the attribute) works correctly — no manual rgb() decomposition.

Fill-bar presets (level, pulse) use the color directly as an SVG fill / stop-color attribute, with the same benefit.

_turbPattern Helper

All turbulence-based presets (fluid, plasma, flow) use the shared _turbPattern(id, turbPrim, colorPrim, W, H, sx, sy) helper. The key insight is:

<filter> applied to a <rect> INSIDE a <pattern>
    evaluates in the pattern tile's LOCAL coordinate system
    → every tile produces identical output
    → animating patternTransform translate is always seam-free

The old feTile + feOffset approach failed because browsers clip feTile output to the filter region. The inner-filter-in-pattern approach produces a true infinite repeat.

_turbPattern emits:

svg
<filter id="stex-inner-{id}" filterUnits="userSpaceOnUse" …>
  {turbPrim}          <!-- feTurbulence; result must be "turb" -->
  {colorPrim}         <!-- colour/composite stages -->
</filter>
<pattern id="stex-pattern-{id}" …>
  <rect filter="url(#stex-inner-{id})" …/>
  <animateTransform …/>    <!-- seamless scroll -->
</pattern>

Template Support

All config string values support synchronous JS templates ([[[…]]]) and {token} substitution, evaluated at render time via LCARdSCardTemplateEvaluator. No async Jinja2 is supported here (this runs synchronously on every render).

The fill_pct field additionally supports two template syntaxes:

yaml
# Form 1 — direct string template (evaluated as a numeric value)
shape_texture:
  preset: level
  config:
    fill_pct: "[[[return entity.attributes.battery_level ?? 0]]]"

# Form 2 — object with template key and fallback default
shape_texture:
  preset: level
  config:
    fill_pct:
      default: 0
      template: "[[[return entity.attributes.battery_level ?? 0]]]"

In Form 2, default is used if the template evaluation fails or returns a non-numeric result.

Any other config string field can use the same template syntax:

yaml
shape_texture:
  preset: grid
  config:
    color: "[[[return entity.state === 'on' ? 'rgba(0,200,100,0.7)' : 'rgba(200,0,0,0.5)']]]"
    scroll_speed_x: "{entity.attributes.speed_override}"

State-based object maps (active/inactive/default keys) remain supported for color, opacity, and speed as before.


Rules Engine Integration

The Rules Engine can patch shape_texture via apply.overlays. Standard template strings in patch values are evaluated automatically. For continuous numeric mapping, fill_pct (and any other numeric config field) also supports map_range descriptors, resolved by _evaluateTemplatesInPatches before the patch reaches the card:

yaml
rules:
  - id: tank_level
    when:
      entity: sensor.tank_level
      above: -1
    apply:
      overlays:
        my-tank-button:
          shape_texture:
            config:
              fill_pct:
                map_range:
                  entity: sensor.tank_level
                  input: [0, 100]
                  output: [0, 100]

Because _resolveShapeTextureConfig() reads directly from this.config at every render (no result is cached), rule patches that modify shape_texture via _applyRulePatches() are automatically reflected on the next render cycle without any extra invalidation step.


Key Files

FileRole
src/core/packs/textures/presets/index.jsSHAPE_TEXTURE_PRESETS registry; _turbPattern helper
src/core/packs/textures/ShapeTextureRenderer.jsSVG string generator
src/core/packs/textures/index.jsRe-exports
src/core/packs/lcards-textures-pack.jsPack metadata
src/editor/components/lcards-shape-texture-editor.jsEditor UI component

See Also