diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml
new file mode 100644
index 00000000..e88ffb95
--- /dev/null
+++ b/.github/workflows/wiki.yml
@@ -0,0 +1,22 @@
+name: Sync Wiki
+
+on:
+ push:
+ branches: [ "master" ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Sync Wiki
+ uses: joeizzard/action-wiki-sync@master
+ with:
+ username: JakeStanger
+ access_token: ${{ secrets.GITHUB_TOKEN }}
+ wiki_folder: docs
+ commit_username: 'Jake Stanger'
+ commit_email: 'mail@jstanger.dev'
+ commit_message: 'sync wiki from main repo'
\ No newline at end of file
diff --git a/docs/Configuration guide.md b/docs/Configuration guide.md
new file mode 100644
index 00000000..3b7b3dd8
--- /dev/null
+++ b/docs/Configuration guide.md
@@ -0,0 +1,268 @@
+By default, you get a single bar at the bottom of all your screens.
+To change that, you'll unsurprisingly need a config file.
+
+## 1. Create config file
+
+The config file lives inside the `ironbar` directory in your XDG_CONFIG_DIR, which is usually `~/.config/ironbar`.
+
+Ironbar supports a range of configuration formats, so you can pick your favourite:
+
+- `config.json`
+- `config.toml`
+- `config.yaml`
+- `config.corn` (Experimental, includes variable support for re-using blocks.
+ See [here](https://github.com/jakestanger/corn) for info)
+
+You can also override the default config path using the `IRONBAR_CONFIG` environment variable.
+
+## 2. Pick your use-case
+
+Ironbar gives you a few ways to configure the bar to suit your needs.
+This allows you to keep your config simple and relatively flat if your use-case is simple,
+and make it more complex if required.
+
+### a) I want the same bar across all monitors
+
+Place the bar config inside the top-level object. This is automatically applied to each of your monitors.
+
+
+JSON
+
+```json
+{
+ "position": "bottom",
+ "height": 42,
+ "start": [],
+ "center": [],
+ "end": []
+}
+```
+
+
+
+
+TOML
+
+```toml
+position = "bottom"
+height = 42
+start = []
+center = []
+end = []
+```
+
+
+
+
+YAML
+
+```yaml
+position: "bottom"
+height: 42
+start: [ ]
+center: [ ]
+end: [ ]
+```
+
+
+
+
+Corn
+
+```
+{
+ position = "bottom"
+ height = 42
+ start = []
+ center = []
+ end = []
+}
+```
+
+
+
+### b) I want my config to differ across one or more monitors
+
+Create a map/object called `monitors` inside the top-level object.
+Each of the map's keys should be an output name,
+and each value should be an object containing the bar config.
+
+To find your output names, run `wayland-info | grep wl_output -A1`.
+
+
+JSON
+
+```json
+{
+ "monitors": {
+ "DP-1": {
+ "start": []
+ },
+ "DP-2": {
+ "position": "bottom",
+ "height": 30,
+ "start": []
+ }
+ }
+}
+```
+
+
+
+
+TOML
+
+```toml
+[monitors]
+
+[monitors.DP-1]
+start = []
+
+[monitors.DP-2]
+position = "bottom"
+height = 30
+start = []
+```
+
+
+
+
+YAML
+
+```yaml
+monitors:
+ DP-1:
+ start: [ ]
+ DP-2:
+ position: "bottom"
+ height: 30
+ start: [ ]
+```
+
+
+
+
+Corn
+
+```
+{
+ monitors.DP-1.start = []
+ monitors.DP-2 = {
+ position = "bottom"
+ height = 30
+ start = []
+ }
+}
+```
+
+
+
+### c) I want one or more monitors to have multiple bars
+
+Create a map/object called `monitors` inside the top-level object.
+Each of the map's keys should be an output name.
+If you want the screen to have multiple bars, use an array of bar config objects.
+If you want the screen to have a single bar, use an object.
+
+To find your output names, run `wayland-info | grep wl_output -A1`.
+
+
+JSON
+
+```json
+{
+ "monitors": {
+ "DP-1": [
+ {
+ "start": []
+ },
+ {
+ "position": "top",
+ "start": []
+ }
+ ],
+ "DP-2": {
+ "position": "bottom",
+ "height": 30,
+ "start": []
+ }
+ }
+}
+```
+
+
+
+
+TOML
+
+```toml
+[monitors]
+
+[[monitors.DP-1]]
+start = []
+
+[[monitors.DP-2]]
+position = "top"
+start = []
+
+[monitors.DP-2]
+position = "bottom"
+height = 30
+start = []
+```
+
+
+
+
+YAML
+
+```yaml
+monitors:
+ DP-1:
+ - start: [ ]
+ - position: "top"
+ start: [ ]
+ DP-2:
+ position: "bottom"
+ height: 30
+ start: [ ]
+```
+
+
+
+
+Corn
+
+```
+{
+ monitors.DP-1 = [
+ { start = [] }
+ { position = "top" start = [] }
+ ]
+ monitors.DP-2 = {
+ position = "bottom"
+ height = 30
+ start = []
+ }
+}
+```
+
+
+
+## 3. Write your bar config(s)
+
+Once you have the basic config structure set up, it's time to actually configure your bar(s).
+
+The following table describes each of the top-level bar config options.
+For details on available modules and each of their config options, check the sidebar.
+
+| Name | Type | Default | Description |
+|-------------------|----------------------------------------|----------|-----------------------------------------------------------------------------------------|
+| `position` | `top` or `bottom` or `left` or `right` | `bottom` | The bar's position on screen. |
+| `anchor_to_edges` | `boolean` | `false` | Whether to anchor the bar to the edges of the screen. Setting to false centres the bar. |
+| `height` | `integer` | `42` | The bar's height in pixels. |
+| `start` | `Module[]` | `[]` | Array of left or top modules. |
+| `center` | `Module[]` | `[]` | Array of center modules. |
+| `end` | `Module[]` | `[]` | Array of right or bottom modules. |
+
+Check [here](config) for an example config file for a fully configured bar in each format.
\ No newline at end of file
diff --git a/docs/Home.md b/docs/Home.md
new file mode 100644
index 00000000..04d17898
--- /dev/null
+++ b/docs/Home.md
@@ -0,0 +1,4 @@
+Welcome to the Ironbar wiki.
+
+Detail about each module, and their configuration and styling options can be found on the sidebar.
+You can also find an example configuration and stylesheet there.
\ No newline at end of file
diff --git a/docs/Styling guide.md b/docs/Styling guide.md
new file mode 100644
index 00000000..3c350e84
--- /dev/null
+++ b/docs/Styling guide.md
@@ -0,0 +1,20 @@
+Ironbar ships with no styles by default, so will fall back to the default GTK styles.
+
+To style the bar, create a file at `~/.config/ironbar/style.css`.
+
+Style changes are hot-loaded so there is no need to reload the bar.
+
+A reminder: since the bar is GTK-based, it uses GTK's implementation of CSS,
+which only includes a subset of the full web spec (plus a few non-standard properties).
+
+The below table describes the selectors provided by the bar itself.
+Information on styling individual modules can be found on their pages in the sidebar.
+
+| Selector | Description |
+|----------------|-------------------------------------------|
+| `.background` | Top-level window |
+| `#bar` | Bar root box |
+| `#bar #start` | Bar left or top modules container box |
+| `#bar #center` | Bar center modules container box |
+| `#bar #end` | Bar right or bottom modules container box |
+| `.container` | All of the above |
diff --git a/docs/Sys-Info.md b/docs/Sys-Info.md
new file mode 100644
index 00000000..dca0d7be
--- /dev/null
+++ b/docs/Sys-Info.md
@@ -0,0 +1,176 @@
+Displays one or more labels containing system information.
+
+Separating information across several labels allows for styling each one independently.
+Pango markup is supported.
+
+![Screenshot showing sys-info module with widgets for all of the types of formatting tokens](https://user-images.githubusercontent.com/5057870/196059090-4056d083-69f0-4e6f-9673-9e35dc29d9f0.png)
+
+
+## Configuration
+
+> Type: `sys-info`
+
+| Name | Type | Default | Description |
+|--------------------|--------------------|---------|--------------------------------------------------------------------------------------------------------------------------------|
+| `format` | `string[]` | `null` | Array of strings including formatting tokens. For available tokens see below. |
+| `interval` | `integer` or `Map` | `5` | Seconds between refreshing. Can be a single value for all data or a map of individual refresh values for different data types. |
+| `interval.memory` | `integer` | `5` | Seconds between refreshing memory data |
+| `interval.cpu` | `integer` | `5` | Seconds between refreshing cpu data |
+| `interval.temps` | `integer` | `5` | Seconds between refreshing temperature data |
+| `interval.disks` | `integer` | `5` | Seconds between refreshing disk data |
+| `interval.network` | `integer` | `5` | Seconds between refreshing network data |
+
+
+JSON
+
+```json
+{
+ "end": [
+ {
+ "format": [
+ " {cpu-percent}% | {temp-c:k10temp-Tccd1}°C",
+ " {memory-used} / {memory-total} GB ({memory-percent}%)",
+ "| {swap-used} / {swap-total} GB ({swap-percent}%)",
+ " {disk-used:/} / {disk-total:/} GB ({disk-percent:/}%)",
+ "李 {net-down:enp39s0} / {net-up:enp39s0} Mbps",
+ "猪 {load-average:1} | {load-average:5} | {load-average:15}",
+ " {uptime}"
+ ],
+ "interval": {
+ "cpu": 1,
+ "disks": 300,
+ "memory": 30,
+ "networks": 3,
+ "temps": 5
+ },
+ "type": "sys-info"
+ }
+ ]
+}
+```
+
+
+
+
+TOML
+
+```toml
+[[end]]
+type = 'sys-info'
+format = [
+ ' {cpu-percent}% | {temp-c:k10temp-Tccd1}°C',
+ ' {memory-used} / {memory-total} GB ({memory-percent}%)',
+ '| {swap-used} / {swap-total} GB ({swap-percent}%)',
+ ' {disk-used:/} / {disk-total:/} GB ({disk-percent:/}%)',
+ '李 {net-down:enp39s0} / {net-up:enp39s0} Mbps',
+ '猪 {load-average:1} | {load-average:5} | {load-average:15}',
+ ' {uptime}',
+]
+
+[end.interval]
+cpu = 1
+disks = 300
+memory = 30
+networks = 3
+temps = 5
+
+
+```
+
+
+
+
+YAML
+
+```yaml
+end:
+- format:
+ - {cpu-percent}% | {temp-c:k10temp-Tccd1}°C
+ - {memory-used} / {memory-total} GB ({memory-percent}%)
+ - '| {swap-used} / {swap-total} GB ({swap-percent}%)'
+ - {disk-used:/} / {disk-total:/} GB ({disk-percent:/}%)
+ - 李 {net-down:enp39s0} / {net-up:enp39s0} Mbps
+ - 猪 {load-average:1} | {load-average:5} | {load-average:15}
+ - {uptime}
+ interval:
+ cpu: 1
+ disks: 300
+ memory: 30
+ networks: 3
+ temps: 5
+ type: sys-info
+```
+
+
+
+
+Corn
+
+```corn
+{
+ end = [
+ {
+ type = "sys-info"
+
+ interval.memory = 30
+ interval.cpu = 1
+ interval.temps = 5
+ interval.disks = 300
+ interval.networks = 3
+
+ format = [
+ " {cpu-percent}% | {temp-c:k10temp-Tccd1}°C"
+ " {memory-used} / {memory-total} GB ({memory-percent}%)"
+ "| {swap-used} / {swap-total} GB ({swap-percent}%)"
+ " {disk-used:/} / {disk-total:/} GB ({disk-percent:/}%)"
+ "李 {net-down:enp39s0} / {net-up:enp39s0} Mbps"
+ "猪 {load-average:1} | {load-average:5} | {load-average:15}"
+ " {uptime}"
+ ]
+ }
+ ]
+}
+```
+
+
+
+### Formatting Tokens
+
+The following tokens can be used in the `format` configuration option:
+
+| Token | Description |
+|--------------------------|------------------------------------------------------------------------------------|
+| **CPU** | |
+| `{cpu-percent}` | Total CPU utilisation percentage |
+| **Memory** | |
+| `{memory-free}` | Memory free in GB. |
+| `{memory-used}` | Memory used in GB. |
+| `{memory-total}` | Memory total in GB. |
+| `{memory-percent}` | Memory utilisation percentage. |
+| `{swap-free}` | Swap free in GB. |
+| `{swap-used}` | Swap used in GB. |
+| `{swap-total}` | Swap total in GB. |
+| `{swap-percent}` | Swap utilisation percentage. |
+| **Temperature** | |
+| `{temp-c:[sensor]}` | Temperature in degrees C. Replace `[sensor]` with the sensor label. |
+| `{temp-f:[sensor]}` | Temperature in degrees F. Replace `[sensor]` with the sensor label. |
+| **Disk** | |
+| `{disk-free:[mount]}` | Disk free space in GB. Replace `[mount]` with the disk mountpoint. |
+| `{disk-used:[mount]}` | Disk used space in GB. Replace `[mount]` with the disk mountpoint. |
+| `{disk-total:[mount]}` | Disk total space in GB. Replace `[mount]` with the disk mountpoint. |
+| `{disk-percent:[mount]}` | Disk utilisation percentage. Replace `[mount]` with the disk mountpoint. |
+| **Network** | |
+| `{net-down:[adapter]}` | Average network download speed in Mbps. Replace `[adapter]` with the adapter name. |
+| `{net-up:[adapter]}` | Average network upload speed in Mbps. Replace `[adapter]` with the adapter name. |
+| **System** | |
+| `{load-average:1}` | 1-minute load average. |
+| `{load-average:5}` | 5-minute load average. |
+| `{load-average:15}` | 15-minute load average. |
+| `{uptime}` | System uptime formatted as `HH:mm`. |
+
+## Styling
+
+| Selector | Description |
+|------------------|------------------------------|
+| `#sysinfo` | Sysinfo widget box |
+| `#sysinfo #item` | Individual information label |
\ No newline at end of file
diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md
new file mode 100644
index 00000000..d274cec9
--- /dev/null
+++ b/docs/_Sidebar.md
@@ -0,0 +1,21 @@
+# Guides
+
+- [Configuration guide](configuration-guide)
+- [Styling guide](styling-guide)
+
+# Examples
+
+- [Config](config)
+- [Stylesheet](stylesheet)
+
+# Modules
+
+- [Clock](clock)
+- [Custom](custom)
+- [Focused](focused)
+- [Launcher](launcher)
+- [MPD](mpd)
+- [Script](script)
+- [Sys-info](sys-info)
+- [Tray](tray)
+- [Workspaces](workspaces)
\ No newline at end of file
diff --git a/docs/examples/Config.md b/docs/examples/Config.md
new file mode 100644
index 00000000..ea808a12
--- /dev/null
+++ b/docs/examples/Config.md
@@ -0,0 +1,202 @@
+The below config shows a module of each type being used.
+
+The Corn format makes heavy use of variables
+to show how module configs can be easily referenced to improve readability
+and reduce config length when using multiple bars.
+
+
+JSON
+
+```json
+{
+ "start": [
+ {
+ "all_monitors": false,
+ "name_map": {
+ "1": "ﭮ",
+ "2": "",
+ "3": "",
+ "Code": "",
+ "Games": ""
+ },
+ "type": "workspaces"
+ },
+ {
+ "favorites": [
+ "firefox",
+ "discord",
+ "Steam"
+ ],
+ "icon_theme": "Paper",
+ "show_icons": true,
+ "show_names": false,
+ "type": "launcher"
+ }
+ ],
+ "end": [
+ {
+ "music_dir": "/home/jake/Music",
+ "type": "mpd"
+ },
+ {
+ "host": "chloe:6600",
+ "type": "mpd"
+ },
+ {
+ "path": "/home/jake/bin/phone-battery",
+ "type": "script"
+ },
+ {
+ "format": [
+ "{cpu-percent}% ",
+ "{memory-percent}% "
+ ],
+ "type": "sys-info"
+ },
+ {
+ "type": "clock"
+ }
+ ]
+}
+```
+
+
+
+
+TOML
+
+```toml
+[[start]]
+all_monitors = false
+type = 'workspaces'
+
+[start.name_map]
+1 = 'ﭮ'
+2 = ''
+3 = ''
+Code = ''
+Games = ''
+
+[[start]]
+icon_theme = 'Paper'
+show_icons = true
+show_names = false
+type = 'launcher'
+favorites = [
+ 'firefox',
+ 'discord',
+ 'Steam',
+]
+
+[[end]]
+music_dir = '/home/jake/Music'
+type = 'mpd'
+
+[[end]]
+host = 'chloe:6600'
+type = 'mpd'
+
+[[end]]
+path = '/home/jake/bin/phone-battery'
+type = 'script'
+
+[[end]]
+type = 'sys-info'
+format = [
+ '{cpu-percent}% ',
+ '{memory-percent}% ',
+]
+
+[[end]]
+type = 'clock'
+```
+
+
+
+
+YAML
+
+```yaml
+---
+start:
+ - all_monitors: false
+ name_map:
+ "1": ﭮ
+ "2":
+ "3":
+ Code:
+ Games:
+ type: workspaces
+ - favorites:
+ - firefox
+ - discord
+ - Steam
+ icon_theme: Paper
+ show_icons: true
+ show_names: false
+ type: launcher
+end:
+ - music_dir: /home/jake/Music
+ type: mpd
+ - host: "chloe:6600"
+ type: mpd
+ - path: /home/jake/bin/phone-battery
+ type: script
+ - format:
+ - "{cpu-percent}% "
+ - "{memory-percent}% "
+ type: sys-info
+ - type: clock
+```
+
+
+
+Corn
+
+```corn
+let {
+ $workspaces = {
+ type = "workspaces"
+ all_monitors = false
+ name_map = {
+ 1 = "ﭮ"
+ 2 = ""
+ 3 = ""
+ Games = ""
+ Code = ""
+ }
+ }
+
+ $launcher = {
+ type = "launcher"
+ favorites = ["firefox" "discord" "Steam"]
+ show_names = false
+ show_icons = true
+ icon_theme = "Paper"
+ }
+
+ $mpd_local = { type = "mpd" music_dir = "/home/jake/Music" }
+ $mpd_server = { type = "mpd" host = "chloe:6600" }
+
+ $sys_info = {
+ type = "sys-info"
+ format = ["{cpu-percent}% " "{memory-percent}% "]
+ }
+
+ $tray = { type = "tray" }
+ $clock = { type = "clock" }
+
+ $phone_battery = {
+ type = "script"
+ path = "/home/jake/bin/phone-battery"
+ }
+
+ $start = [ $workspaces $launcher ]
+ $end = [ $mpd_local $mpd_server $phone_battery $sys_info $clock ]
+}
+in {
+ start = $start
+ end = $end
+}
+```
+
\ No newline at end of file
diff --git a/docs/examples/Stylesheet.md b/docs/examples/Stylesheet.md
new file mode 100644
index 00000000..08efa521
--- /dev/null
+++ b/docs/examples/Stylesheet.md
@@ -0,0 +1,142 @@
+The below example is a full stylesheet for all modules:
+
+```css
+* {
+ /* a nerd font is required to be installed for icons */
+ font-family: Noto Sans Nerd Font, sans-serif;
+ font-size: 16px;
+ border: none;
+}
+
+#bar {
+ border-top: 1px solid #424242;
+}
+
+.container {
+ background-color: #2d2d2d;
+}
+
+.container#end > * + * {
+ margin-left: 20px;
+}
+
+.popup {
+ background-color: #2d2d2d;
+ border: 1px solid #424242;
+}
+
+#workspaces .item {
+ color: white;
+ background-color: #2d2d2d;
+ border-radius: 0;
+}
+
+#workspaces .item.focused {
+ box-shadow: inset 0 -3px;
+ background-color: #1c1c1c;
+}
+
+#workspaces *:not(.focused):hover {
+ box-shadow: inset 0 -3px;
+}
+
+#launcher .item {
+ border-radius: 0;
+ background-color: #2d2d2d;
+ margin-right: 4px;
+}
+
+#launcher .item:not(.focused):hover {
+ background-color: #1c1c1c;
+}
+
+#launcher .open {
+ border-bottom: 2px solid #6699cc;
+}
+
+#launcher .focused {
+ color: white;
+ background-color: black;
+ border-bottom: 4px solid #6699cc;
+}
+
+#launcher .urgent {
+ color: white;
+ background-color: #8f0a0a;
+}
+
+#script {
+ color: white;
+}
+
+#sysinfo {
+ color: white;
+}
+
+#tray .item {
+ background-color: #2d2d2d;
+}
+
+#mpd {
+ background-color: #2d2d2d;
+ color: white;
+}
+
+#popup-mpd {
+ color: white;
+ padding: 1em;
+}
+
+#popup-mpd #album-art {
+ margin-right: 1em;
+}
+
+#popup-mpd #title .icon, #popup-mpd #title .label {
+ font-size: 1.7em;
+}
+
+#popup-mpd #controls * {
+ border-radius: 0;
+ background-color: #2d2d2d;
+ color: white;
+}
+
+#popup-mpd #controls *:disabled {
+ color: #424242;
+}
+
+#clock {
+ color: white;
+ background-color: #2d2d2d;
+ font-weight: bold;
+}
+
+#popup-clock {
+ padding: 1em;
+}
+
+#popup-clock #calendar-clock {
+ color: white;
+ font-size: 2.5em;
+ padding-bottom: 0.1em;
+}
+
+#popup-clock #calendar {
+ background-color: #2d2d2d;
+ color: white;
+}
+
+#popup-clock #calendar .header {
+ padding-top: 1em;
+ border-top: 1px solid #424242;
+ font-size: 1.5em;
+}
+
+#popup-clock #calendar:selected {
+ background-color: #6699cc;
+}
+
+#focused {
+ color: white;
+}
+```
\ No newline at end of file
diff --git a/docs/modules/Clock.md b/docs/modules/Clock.md
new file mode 100644
index 00000000..d2e84e9a
--- /dev/null
+++ b/docs/modules/Clock.md
@@ -0,0 +1,77 @@
+Displays the current date and time.
+Clicking on the widget opens a popup with the time and a calendar.
+
+![Screenshot of clock widget with popup open](https://user-images.githubusercontent.com/5057870/184540521-2278bdec-9742-46f0-9ac2-58a7b6f6ea1d.png)
+
+
+## Configuration
+
+> Type: `clock`
+
+| Name | Type | Default | Description |
+|----------|--------|------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
+| `format` | `string` | `%d/%m/%Y %H:%M` | Date/time format string. Detail on available tokens can be found here: |
+
+
+JSON
+
+```json
+{
+ "end": [
+ {
+ "type": "clock",
+ "format": "%d/%m/%Y %H:%M"
+ }
+ ]
+}
+
+```
+
+
+
+
+TOML
+
+```toml
+[[end]]
+type = "clock"
+format = "%d/%m/%Y %H:%M"
+```
+
+
+
+
+YAML
+
+```yaml
+end:
+ - type: "clock"
+ format: "%d/%m/%Y %H:%M"
+```
+
+
+
+
+Corn
+
+```corn
+{
+ end = [
+ {
+ type = "clock"
+ format = "%d/%m/%Y %H:%M"
+ }
+ ]
+}
+```
+
+
+
+## Styling
+
+| Selector | Description |
+|-------------------------------|------------------------------------------------------------------------------------|
+| `#clock` | Clock widget button |
+| `#popup-clock` | Clock popup box |
+| `#popup-clock #calendar-clock` | Clock inside the popup |
+| `#popup-clock #calendar` | Calendar widget inside the popup. GTK provides some OOTB styling options for this. |
\ No newline at end of file
diff --git a/docs/modules/Custom.md b/docs/modules/Custom.md
new file mode 100644
index 00000000..04c0f07f
--- /dev/null
+++ b/docs/modules/Custom.md
@@ -0,0 +1,237 @@
+Allows you to compose custom modules consisting of multiple widgets, including popups.
+Buttons can interact with the bar or execute commands on click.
+
+![Custom module with a button on the bar, and the popup open. The popup contains a header, shutdown button and restart button.](https://user-images.githubusercontent.com/5057870/196058785-042ef171-7e77-4d5c-921a-eca03c6424bd.png)
+
+## Configuration
+
+> Type: `custom`
+
+This module can be quite fiddly to configure as you effectively have to build a tree of widgets by hand.
+It is well worth looking at the examples.
+
+| Name | Type | Default | Description |
+|---------|------------|---------|--------------------------------------|
+| `class` | `string` | `null` | Container class name. |
+| `bar` | `Widget[]` | `null` | List of widgets to add to the bar. |
+| `popup` | `Widget[]` | `[]` | List of widgets to add to the popup. |
+
+### `Widget`
+
+| Name | Type | Default | Description |
+|---------------|------------------------------|--------------|---------------------------------------------------------------------------|
+| `widget_type` | `box` or `label` or `button` | `null` | Type of GTK widget to create. |
+| `name` | `string` | `null` | Widget name. |
+| `class` | `string` | `null` | Widget class name. |
+| `label` | `string` | `null` | [`label` and `button`] Widget text label. Pango markup supported. |
+| `exec` | `string` | `null` | [`button`] Command to execute. More on this [below](#commands). |
+| `orientation` | `horizontal` or `vertical` | `horizontal` | [`box`] Whether child widgets should be horizontally or vertically added. |
+| `widgets` | `Widget[]` | `[]` | [`box`] List of widgets to add to this box. |
+
+### Commands
+
+Buttons can execute commands that interact with the bar,
+as well as any arbitrary shell command.
+
+To execute shell commands, prefix them with an `!`.
+For example, if you want to run `~/.local/bin/my-script.sh` on click,
+you'd set `exec` to `!~/.local/bin/my-script.sh`.
+
+The following bar commands are supported:
+
+- `popup:toggle`
+- `popup:open`
+- `popup:close`
+
+XML is arguably better-suited and easier to read for this sort of markup,
+but currently not supported.
+Nonetheless, it may be worth comparing the examples to the below equivalent
+to help get your head around what's going on:
+
+
+```xml
+
+
+```
+
+
+JSON
+
+```json
+{
+ "end": [
+ {
+ "type": "custom",
+ "bar": [
+ {
+ "exec": "popup:toggle",
+ "label": "",
+ "name": "power-btn",
+ "type": "button"
+ }
+ ],
+ "class": "power-menu",
+ "popup": [
+ {
+ "orientation": "vertical",
+ "type": "box",
+ "widgets": [
+ {
+ "label": "Power menu",
+ "name": "header",
+ "type": "label"
+ },
+ {
+ "type": "box",
+ "widgets": [
+ {
+ "class": "power-btn",
+ "exec": "!shutdown now",
+ "label": "",
+ "type": "button"
+ },
+ {
+ "class": "power-btn",
+ "exec": "!reboot",
+ "label": "",
+ "type": "button"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+```
+
+
+
+
+TOML
+
+```toml
+[[end]]
+class = 'power-menu'
+type = 'custom'
+
+[[end.bar]]
+exec = 'popup:toggle'
+label = ''
+name = 'power-btn'
+type = 'button'
+
+[[end.popup]]
+orientation = 'vertical'
+type = 'box'
+
+[[end.popup.widgets]]
+label = 'Power menu'
+name = 'header'
+type = 'label'
+
+[[end.popup.widgets]]
+type = 'box'
+
+[[end.popup.widgets.widgets]]
+class = 'power-btn'
+exec = '!shutdown now'
+label = ''''''
+type = 'button'
+
+[[end.popup.widgets.widgets]]
+class = 'power-btn'
+exec = '!reboot'
+label = ''''''
+type = 'button'
+```
+
+
+
+
+YAML
+
+```yaml
+end:
+- type: custom
+ bar:
+ - exec: popup:toggle
+ label:
+ name: power-btn
+ type: button
+ class: power-menu
+ popup:
+ - orientation: vertical
+ type: box
+ widgets:
+ - label: Power menu
+ name: header
+ type: label
+ - type: box
+ widgets:
+ - class: power-btn
+ exec: '!shutdown now'
+ label:
+ type: button
+ - class: power-btn
+ exec: '!reboot'
+ label:
+ type: button
+```
+
+
+
+
+Corn
+
+```corn
+let {
+ $power_menu = {
+ type = "custom"
+ class = "power-menu"
+
+ bar = [ { type = "button" name="power-btn" label = "" exec = "popup:toggle" } ]
+
+ popup = [ {
+ type = "box"
+ orientation = "vertical"
+ widgets = [
+ { type = "label" name = "header" label = "Power menu" }
+ {
+ type = "box"
+ widgets = [
+ { type = "button" class="power-btn" label = "" exec = "!shutdown now" }
+ { type = "button" class="power-btn" label = "" exec = "!reboot" }
+ ]
+ }
+ ]
+ } ]
+ }
+} in {
+ end = [ $power_menu ]
+}
+```
+
+
+
+## Styling
+
+Since the widgets are all custom, you can target them using `#name` and `.class`.
+
+| Selector | Description |
+|-----------|-------------------------|
+| `#custom` | Custom widget container |
\ No newline at end of file
diff --git a/docs/modules/Focused.md b/docs/modules/Focused.md
new file mode 100644
index 00000000..be656326
--- /dev/null
+++ b/docs/modules/Focused.md
@@ -0,0 +1,90 @@
+Displays the title and/or icon of the currently focused window.
+
+![Screenshot of focused widget, showing this page open on firefox](https://user-images.githubusercontent.com/5057870/184714118-c1fb1c67-cd8c-4cc0-b5cd-6faccff818ac.png)
+
+
+## Configuration
+
+> Type: `focused`
+
+| Name | Type | Default | Description |
+|--------------|-----------|---------|---------------------------------|
+| `show_icon` | `boolean` | `true` | Whether to show the app's icon |
+| `show_title` | `boolean` | `true` | Whether to show the app's title |
+| `icon_size` | `integer` | `32` | Size of icon in pixels |
+| `icon_theme` | `string` | `null` | GTK icon theme to use |
+
+
+JSON
+
+```json
+{
+ "end": [
+ {
+ "type": "focused",
+ "show_icon": true,
+ "show_title": true,
+ "icon_size": 32,
+ "icon_theme": "Paper"
+ }
+ ]
+}
+
+```
+
+
+
+
+TOML
+
+```toml
+[[end]]
+type = "focused"
+show_icon = true
+show_title = true
+icon_size = 32
+icon_theme = "Paper"
+```
+
+
+
+
+YAML
+
+```yaml
+end:
+ - type: "focused"
+ show_icon: true
+ show_title: true
+ icon_size: 32
+ icon_theme: "Paper"
+```
+
+
+
+
+Corn
+
+```corn
+{
+ end = [
+ {
+ type = "focused"
+ show_icon = true
+ show_title = true
+ icon_size = 32
+ icon_theme = "Paper"
+ }
+ ]
+}
+```
+
+
+
+## Styling
+
+| Selector | Description |
+|--------------------------|--------------------|
+| `#focused` | Focused widget box |
+| `#focused #icon` | App icon |
+| `#focused #label` | App name |
\ No newline at end of file
diff --git a/docs/modules/Launcher.md b/docs/modules/Launcher.md
new file mode 100644
index 00000000..3ca99367
--- /dev/null
+++ b/docs/modules/Launcher.md
@@ -0,0 +1,103 @@
+Windows-style taskbar that displays running windows, grouped by program.
+Hovering over a program with multiple windows open shows a popup with each window.
+Clicking an icon/popup item focuses or launches the program.
+Optionally displays a launchable set of favourites.
+
+![Screenshot showing several open applications, including a focused terminal.](https://user-images.githubusercontent.com/5057870/184540058-120e190e-2a45-4167-99c7-ed76482d1f16.png)
+
+## Configuration
+
+> Type: `launcher`
+
+| | Type | Default | Description |
+|--------------|------------|---------|-----------------------------------------------------------------------------------------------------|
+| `favorites` | `string[]` | `[]` | List of app IDs (or classes) to always show at the start of the launcher |
+| `show_names` | `boolean` | `false` | Whether to show app names on the button label. Names will still show on tooltips when set to false. |
+| `show_icons` | `boolean` | `true` | Whether to show app icons on the button. |
+| `icon_theme` | `string` | `null` | GTK icon theme to use. |
+
+
+JSON
+
+```json
+{
+ "start": [
+ {
+ "type": "launcher",
+ "favourites": [
+ "firefox",
+ "discord"
+ ],
+ "show_names": false,
+ "show_icons": true,
+ "icon_theme": "Paper"
+ }
+ ]
+}
+
+
+```
+
+
+
+
+TOML
+
+```toml
+[[start]]
+type = "launcher"
+favorites = ["firefox", "discord"]
+show_names = false
+show_icons = true
+icon_theme = "Paper"
+```
+
+
+
+
+YAML
+
+```yaml
+start:
+ - type: "launcher"
+ favorites:
+ - firefox
+ - discord
+ show_names: false
+ show_icons: true
+ icon_theme: "Paper"
+```
+
+
+
+
+Corn
+
+```corn
+{
+ start = [
+ {
+ type = "launcher"
+ favorites = ["firefox" "discord"]
+ show_names = false
+ show_icons = true
+ icon_theme = "Paper"
+
+ }
+ ]
+}
+```
+
+
+
+## Styling
+
+| Selector | Description |
+|-------------------------------|--------------------------|
+| `#launcher` | Launcher widget box |
+| `#launcher .item` | App button |
+| `#launcher .item.open` | App button (open app) |
+| `#launcher .item.focused` | App button (focused app) |
+| `#launcher .item.urgent` | App button (urgent app) |
+| `#launcher-popup` | Popup container |
+| `#launcher-popup .popup-item` | Window button in popup |
diff --git a/docs/modules/MPD.md b/docs/modules/MPD.md
new file mode 100644
index 00000000..0cfd8b69
--- /dev/null
+++ b/docs/modules/MPD.md
@@ -0,0 +1,131 @@
+Displays currently playing song from MPD.
+Clicking on the widget opens a popout displaying info about the current song, album art
+and playback controls.
+
+![Screenshot showing MPD widget with track playing with popout open](https://user-images.githubusercontent.com/5057870/184539664-a8f3ad5b-69c0-492d-a27d-82303c09a347.png)
+
+## Configuration
+
+> Type: `mpd`
+
+| | Type | Default | Description |
+|----------------|----------|-----------------------------|-----------------------------------------------------------------------|
+| `host` | `string` | `localhost:6600` | TCP or Unix socket for the MPD server. |
+| `format` | `string` | `{icon} {title} / {artist}` | Format string for the widget. More info below. |
+| `icons.play` | `string` | `` | Icon to show when playing. |
+| `icons.pause` | `string` | `` | Icon to show when paused. |
+| `icons.volume` | `string` | `墳` | Icon to show under popup volume slider. |
+| `music_dir` | `string` | `$HOME/Music` | Path to MPD server's music directory on disc. Required for album art. |
+
+
+JSON
+
+```json
+{
+ "start": [
+ {
+ "type": "mpd",
+ "format": "{icon} {title} / {artist}",
+ "icons": {
+ "play": "",
+ "pause": ""
+ },
+ "music_dir": "/home/jake/Music"
+ }
+ ]
+}
+```
+
+
+
+
+TOML
+
+```toml
+[[start]]
+type = "mpd"
+format = "{icon} {title} / {artist}"
+music_dir = "/home/jake/Music"
+
+[[start.icons]]
+play = ""
+pause = ""
+```
+
+
+
+
+YAML
+
+```yaml
+start:
+ - type: "mpd"
+ format: "{icon} {title} / {artist}"
+ icons:
+ play: ""
+ pause: ""
+ music_dir: "/home/jake/Music"
+```
+
+
+
+
+Corn
+
+```corn
+{
+ start = [
+ {
+ type = "mpd"
+ format = "{icon} {title} / {artist}"
+ icons.play = ""
+ icons.pause = ""
+ music_dir = "/home/jake/Music"
+ }
+ ]
+}
+```
+
+
+
+### Formatting Tokens
+
+The following tokens can be used in the `format` config option,
+and will be replaced with values from the currently playing track:
+
+| Token | Description |
+|--------------|--------------------------------------|
+| `{icon}` | Either `icons.play` or `icons.pause` |
+| `{title}` | Title |
+| `{album}` | Album name |
+| `{artist}` | Artist name |
+| `{date}` | Release date |
+| `{track}` | Track number |
+| `{disc}` | Disc number |
+| `{genre}` | Genre |
+| `{duration}` | Duration in `mm:ss` |
+| `{elapsed}` | Time elapsed in `mm:ss` |
+
+## Styling
+
+| Selector | Description |
+|----------------------------------------|------------------------------------------|
+| `#mpd` | Tray widget button |
+| `#popup-mpd` | Popup box |
+| `#popup-mpd #album-art` | Album art image inside popup box |
+| `#popup-mpd #title` | Track title container inside popup box |
+| `#popup-mpd #title .icon` | Track title icon label inside popup box |
+| `#popup-mpd #title .label` | Track title label inside popup box |
+| `#popup-mpd #album` | Track album container inside popup box |
+| `#popup-mpd #album .icon` | Track album icon label inside popup box |
+| `#popup-mpd #album .label` | Track album label inside popup box |
+| `#popup-mpd #artist` | Track artist container inside popup box |
+| `#popup-mpd #artist .icon` | Track artist icon label inside popup box |
+| `#popup-mpd #artist .label` | Track artist label inside popup box |
+| `#popup-mpd #controls` | Controls container inside popup box |
+| `#popup-mpd #controls #btn-prev` | Previous button inside popup box |
+| `#popup-mpd #controls #btn-play-pause` | Play/pause button inside popup box |
+| `#popup-mpd #controls #btn-next` | Next button inside popup box |
+| `#popup-mpd #volume` | Volume container inside popup box |
+| `#popup-mpd #volume #slider` | Volume slider popup box |
+| `#popup-mpd #volume .icon` | Volume icon label inside popup box |
\ No newline at end of file
diff --git a/docs/modules/Script.md b/docs/modules/Script.md
new file mode 100644
index 00000000..6c310b56
--- /dev/null
+++ b/docs/modules/Script.md
@@ -0,0 +1,76 @@
+Executes a script and shows the result of `stdout` on a label.
+Pango markup is supported.
+
+## Configuration
+
+> Type: `script`
+
+| Name | Type | Default | Description |
+|-----------|----------|---------|---------------------------------------------------------|
+| `path` | `string` | `null` | Path to the script on disk |
+| `interval` | `number` | `5000` | Number of milliseconds to wait between executing script |
+
+
+JSON
+
+```json
+{
+ "end": [
+ {
+ "type": "script",
+ "path": "/home/jake/.local/bin/phone-battery",
+ "interval": 5000
+ }
+ ]
+}
+
+```
+
+
+
+
+TOML
+
+```toml
+[[end]]
+type = "script"
+path = "/home/jake/.local/bin/phone-battery"
+interval = 5000
+```
+
+
+
+
+YAML
+
+```yaml
+end:
+ - type: "script"
+ path: "/home/jake/.local/bin/phone-battery"
+ interval : 5000
+```
+
+
+
+
+Corn
+
+```corn
+{
+ end = [
+ {
+ type = "script"
+ path = "/home/jake/.local/bin/phone-battery"
+ interval = 5000
+ }
+ ]
+}
+```
+
+
+
+## Styling
+
+| Selector | Description |
+|---------------|---------------------|
+| `#script` | Script widget label |
\ No newline at end of file
diff --git a/docs/modules/Tray.md b/docs/modules/Tray.md
new file mode 100644
index 00000000..0329bc97
--- /dev/null
+++ b/docs/modules/Tray.md
@@ -0,0 +1,64 @@
+Displays a fully interactive icon tray using the KDE `libappindicator` protocol.
+
+![Screenshot showing icon tray widget](https://user-images.githubusercontent.com/5057870/184540135-78ffd79d-f802-4c79-b09a-05a733dadc55.png)
+
+## Configuration
+
+> Type: `tray`
+
+***This module provides no configuration options.***
+
+
+JSON
+
+```json
+{
+ "end": [
+ {
+ "type": "tray"
+ }
+ ]
+}
+```
+
+
+
+
+TOML
+
+```toml
+[[end]]
+type = "tray"
+```
+
+
+
+
+YAML
+
+```yaml
+end:
+ - type: "tray"
+```
+
+
+
+
+Corn
+
+```corn
+{
+ end = [
+ { type = "tray" }
+ ]
+}
+```
+
+
+
+## Styling
+
+| Selector | Description |
+|---------------|------------------|
+| `#tray` | Tray widget box |
+| `#tray .item` | Tray icon button |
diff --git a/docs/modules/Workspaces.md b/docs/modules/Workspaces.md
new file mode 100644
index 00000000..67ffd74d
--- /dev/null
+++ b/docs/modules/Workspaces.md
@@ -0,0 +1,94 @@
+> ⚠ **This module is currently only supported on Sway**
+
+Shows all current Sway workspaces. Clicking a workspace changes focus to it.
+
+![Screenshot showing workspaces widget using custom icons with browser workspace focused](https://user-images.githubusercontent.com/5057870/184540156-26cfe4ec-ab8d-4e0f-a883-8b641025366b.png)
+
+## Configuration
+
+> Type: `workspaces`
+
+| Name | Type | Default | Description |
+|----------------|-----------------------|---------|----------------------------------------------------------------------------------------------------------------------|
+| `name_map` | `Map` | `{}` | A map of actual workspace names to their display labels. Workspaces use their actual name if not present in the map. |
+| `all_monitors` | `boolean` | `false` | Whether to display workspaces from all monitors. When `false`, only shows workspaces on the current monitor. |
+
+
+JSON
+
+```json
+{
+ "end": [
+ {
+ "type": "workspaces",
+ "name_map": {
+ "1": "",
+ "2": "",
+ "3": ""
+ },
+ "all_monitors": false
+ }
+ ]
+}
+```
+
+
+
+
+TOML
+
+```toml
+[[end]]
+type = "workspaces"
+all_monitors = false
+
+[[end.name_map]]
+1 = ""
+2 = ""
+3 = ""
+
+```
+
+
+
+
+YAML
+
+```yaml
+end:
+ - type: "workspaces"
+ name_map:
+ 1: ""
+ 2: ""
+ 3: ""
+ all_monitors: false
+```
+
+
+
+
+Corn
+
+```corn
+{
+ end = [
+ {
+ type = "workspaces",
+ name_map.1 = ""
+ name_map.2 = ""
+ name_map.3 = ""
+ all_monitors = false
+ }
+ ]
+}
+```
+
+
+
+## Styling
+
+| Selector | Description |
+|-----------------------------|--------------------------------------|
+| `#workspaces` | Workspaces widget box |
+| `#workspaces .item` | Workspace button |
+| `#workspaces .item.focused` | Workspace button (workspace focused) |
\ No newline at end of file