Seelen UI — Theme Guidelines
Reference guide for creating themes. A theme customizes the look of Seelen UI widgets by providing CSS (or SCSS) styles and optional user-configurable CSS variables.
Read resource guidelines first for concepts shared across all resource kinds.
Table of Contents
- Theme Structure
- Targeting Widgets
- Shared Styles
- User-Configurable Settings (CSS Variables)
- System CSS Variables
- Animations and Performance Mode
- Folder Structure
- Inspecting Widgets with DevTools
1. Theme Structure
A theme is a folder with a metadata.yml at its root. The file has four main sections:
# my-theme/metadata.yml
id: "@yourname/my-theme"
metadata:
displayName: My Theme
description: A short description.
tags:
- dark
- minimal
# CSS for individual widgets (one entry per widget)
styles:
"@seelen/fancy-toolbar": !include styles/toolbar.scss
"@seelen/weg": !include styles/weg.css
# CSS applied to every widget (optional)
sharedStyles: !include shared/index.scss
# User-configurable CSS variables (optional)
settings:
- syntax: <color>
name: --my-accent
label: Accent color
initialValue: "#6c63ff"
All four sections are independent — you can have only styles, only settings, or any combination.
2. Targeting Widgets
The styles map uses the widget's ID as the key. Each entry is a CSS or SCSS string that is injected exclusively into
that widget's webview.
A widget ID follows the same format as any resource ID: @creator/name for local and community widgets, or a UUID for
widgets downloaded from the marketplace.
styles:
# Local / community widget (friendly ID)
"@seelen/fancy-toolbar": !include styles/toolbar.scss
# Another creator's widget
"@someone/custom-clock": !include styles/custom-clock.scss
# Downloaded widget identified by UUID
"550e8400-e29b-41d4-a716-446655440000": !include styles/downloaded-widget.scss
You can find a widget's ID in the Seelen UI settings panel, on the widget's detail page. You don't have to target every widget — include only the ones your theme actually reskins.
3. Shared Styles
sharedStyles is a single CSS/SCSS block that is injected into every widget. Use it for things that should look the
same everywhere — scrollbars, focus rings, popover containers, shared components.
sharedStyles: !include shared/index.scss
Important convention: shared styles must not override global bare elements like body, button, or input
directly. Seelen UI widgets use data-skin attributes to opt into shared component styles. Only target those
attributes in shared styles:
/* OK — targets only elements that explicitly opt in */
button[data-skin="solid"] {
background: var(--ui-color);
}
button[data-skin="default"] {
border: 1px solid var(--ui-color);
}
button[data-skin="transparent"] {
background: transparent;
}
input[type="checkbox"][data-skin="switch"] {
/* toggle switch */
}
input[type="range"][data-skin="flat"] {
/* flat slider */
}
select[data-skin="default"] {
/* styled select */
}
/* OK — targets a specific component class */
.slu-standard-popover {
background: var(--color-gray-50);
}
/* Avoid — overrides every button everywhere */
button {
background: red;
}
The sharedStyles field is also the right place to define the performance-mode rule (see
section 6).
4. User-Configurable Settings (CSS Variables)
The settings list lets you expose CSS variables that users can tweak from the Seelen UI settings panel without
touching your files. Each entry defines one variable, the type of input to show, and a default value.
Every variable name must start with -- and follow CSS custom property naming rules (--my-variable,
--toolbar-height, --Color_1, etc.).
Color
Shows a color picker with an alpha slider. Value is stored as a hex string.
- syntax: <color>
name: --my-background
label: Background color
initialValue: "#1e1e2e"
With optional description and tooltip:
- syntax: <color>
name: --my-accent
label: Accent color
description: Used for highlights and interactive elements.
tip: Supports transparency.
initialValue: "#6c63ff"
Use in CSS:
.toolbar {
background-color: var(--my-background);
color: var(--my-accent);
}
Number
Shows a plain number input. Add step to turn it into a slider. Add options to turn it into a dropdown. min and
max constrain the input range.
# Plain input
- syntax: <number>
name: --toolbar-opacity
label: Toolbar opacity
initialValue: 90
min: 0
max: 100
# Slider (step enables the slider UI)
- syntax: <number>
name: --border-radius
label: Corner radius
initialValue: 8
min: 0
max: 24
step: 1
# Dropdown
- syntax: <number>
name: --font-size
label: Font size
initialValue: 13
options:
- 11
- 13
- 15
- 17
Use in CSS:
.ft-bar {
opacity: calc(var(--toolbar-opacity) / 100);
}
.ft-bar-item {
border-radius: calc(var(--border-radius) * 1px);
}
Length / Percentage
Shows a number input where the user can also choose the unit (px, %, em, rem, vw…). Use this when the user should be
free to pick the unit. If you need a fixed unit, use <number> and do the unit conversion in CSS.
- syntax: <length>
name: --item-size
label: Item size
initialValue: 40
initialValueUnit: px
Use in CSS:
.weg-item {
width: var(--item-size);
height: var(--item-size);
}
String
Shows a text input. Use min and max to enforce length limits.
- syntax: <string>
name: --custom-font
label: Font family
initialValue: "Segoe UI"
max: 80
Use in CSS:
* {
font-family:
var(--custom-font), sans-serif;
}
URL
Shows a file picker. The selected file path is stored as a CSS url() value. The initialValue field is ignored — the
variable starts empty until the user picks a file.
- syntax: <url>
name: --custom-background-image
label: Background image
initialValue: ""
Use in CSS:
.ft-bar::before {
content: "";
background-image: var(--custom-background-image);
}
Grouping Settings
Use group to organize settings into collapsible sections. Groups can be nested.
settings:
- syntax: <color>
name: --toolbar-bg
label: Toolbar background
initialValue: "#1e1e2e"
- group:
header: Dock settings
items:
- syntax: <color>
name: --dock-bg
label: Dock background
initialValue: "#313244"
- syntax: <number>
name: --dock-item-size
label: Item size
initialValue: 40
min: 24
max: 72
step: 2
- group:
header: Dock border
items:
- syntax: <color>
name: --dock-border-color
label: Border color
initialValue: "#45475a"
- syntax: <number>
name: --dock-border-width
label: Border width
initialValue: 1
min: 0
max: 4
step: 1
5. System CSS Variables
Seelen UI makes a set of CSS variables available in every widget. You can use these in your stylesheets without declaring them yourself.
Windows accent colors — taken from the user's Windows accent color setting:
--system-accent-color /* main accent */
--system-accent-light-color /* light variant */
--system-accent-lighter-color /* lighter variant */
--system-accent-lightest-color /* lightest variant */
--system-accent-dark-color /* dark variant */
--system-accent-darker-color /* darker variant */
--system-accent-darkest-color /* darkest variant */
6. Animations and Performance Mode
When the user enables extreme performance mode in Seelen UI settings, all animations and transitions defined in themes are automatically forced off by the app. You don't need to do anything special — this is handled for you.
7. Folder Structure
Keep all style paths explicit in metadata.yml using !include. The entrypoint is the single source of truth about
which widgets your theme targets.
my-theme/
├── metadata.yml
├── i18n/
│ ├── display_name.yml
│ └── description.yml
├── shared/
│ └── index.scss
└── styles/
├── toolbar.scss
└── weg.css
# metadata.yml
styles:
"@seelen/fancy-toolbar": !include styles/toolbar.scss
"@seelen/weg": !include styles/weg.css
sharedStyles: !include shared/index.scss
8. Inspecting Widgets with DevTools
Every Seelen UI widget is a webview, so you can inspect its HTML, CSS, and JavaScript exactly like a webpage. Click on any widget to focus it, then press Ctrl + Shift + I to open the browser DevTools for that widget.
From DevTools you can:
- Inspect the DOM and live-edit CSS to prototype styles before writing them in your files.
- Use the Elements panel to find the class names and data attributes your selectors should target.
- Use the Console to test JavaScript expressions (useful when developing widgets or plugins).
- Use the Performance panel to profile rendering and catch expensive animations or layouts.
This works on any widget at any time — no special build or dev mode required.