strand

Strand

A toolkit for Wayland shells. Write bars, launchers, lock screens and panels in one small typed language; save the file and the running shell follows.

InstallRead the guide

shell.strand
bar Top {
  edge: top; height: 32
  split { pad: 0, 12
    start  { text windows.focused?.title ?? "" }
    center { text clock.format("%H:%M") }
    end    { text pct(battery.percent) }
  }
}
Drawn by Strand from this config.

Changes move on springs

Change a value and its prop moves there. Spring curves are theme tokens you can retune, and a prop can choose its own after ~.

Motion

0 px196 pxset on trueset on trueset on falseset on false1.23 s
  • #spatial x on spring(3000, 0.75)
  • #bouncy x on spring(380, 0.75)
  • #timed x on 300ms, bezier(0.2, 0, 0, 1)
0.62 s
shell.strand
state on = false

bar Springs {
  height: 128
  bg: $surface
  col { pad: 16, 20; gap: 14
    row { gap: 12; align: center
      text "default" { width: 64; color: $fg.muted }
      box { width: 220; height: 24; radius: 12; bg: $surface.hi
        box {
          id: spatial
          width: 24; height: 24; radius: 12; bg: $accent
          x: on ? 196 : 0
        }
      }
    }
    row { gap: 12; align: center
      text "bouncy" { width: 64; color: $fg.muted }
      box { width: 220; height: 24; radius: 12; bg: $surface.hi
        box {
          id: bouncy
          width: 24; height: 24; radius: 12; bg: $accent
          x: on ? 196 : 0 ~ $motion.bouncy
        }
      }
    }
    row { gap: 12; align: center
      text "300ms" { width: 64; color: $fg.muted }
      box { width: 220; height: 24; radius: 12; bg: $surface.hi
        box {
          id: timed
          width: 24; height: 24; radius: 12; bg: $accent
          x: on ? 196 : 0 ~ 300ms
        }
      }
    }
  }
}
Drawn by Strand from this config.

Save, and the shell follows

Strand watches your config. When you save, what you changed animates to its new value, and the rest keeps its state, an open popup or the text typed into a launcher.

Every name, field and type is known before the shell starts. A save that does not check leaves the last good shell running, and the check says what is wrong and where, and usually how to fix it.

Live reload

shell.strand
bar Top {
  edge: top; height: 32
  text pct(battery.precent)
}
$ strand check
check::unknown_field

  × `battery` has no field `precent`
   ╭─[shell.strand:3:20]
 2 │   edge: top; height: 32
 3 │   text pct(battery.precent)
   ·                    ───┬───
   ·                       ╰── unknown field
 4 │ }
   ╰────
  help: did you mean `percent`?

The documentation

Strand runs on sway, Hyprland, niri and labwc, and every build is tested on all four.

Using an AI assistant? Every page is also plain Markdown: point it at llms.txt, or give it the whole docs in one file.