Where a theme lives
A theme is a folder with two files: itsmanifest.json and a bundle.json that contains the definition below.
The bundle’s filename is fixed. The loader looks for exactly bundle.json next to the manifest. It skips the theme if the file is missing or doesn’t parse into a JSON object.
The shape of a theme
A theme definition has three parts that set its colors: a basetype, semantic colors, and raw colors. Together they override Discord’s palette.
bundle.json
Base type
type sets the base palette your theme builds on. Override only the colors you want to change. Everything else falls back to this base.
- Midnight
- Darker
- Light
A pure-black base, for OLED/AMOLED-style themes.
Semantic colors
Semantic colors are Discord’s named color roles, such asBACKGROUND_PRIMARY and TEXT_NORMAL. Setting a role recolors every place that role is used, so the result stays consistent across the app.
Each key is the selector name of the color Unbound hooks. A key that doesn’t match any selector does nothing. It’s never looked up, and you get no warning.
Each entry is an object with a type (color for a literal color, or raw to reference a raw color), a value, and an optional opacity.
'color' | 'raw'
color is a literal value. raw points to one of your raw colors, so several roles can share it.string
The color, or the raw key when
type is raw.number
Optional opacity from
0 to 1, applied on top of the parsed color. It replaces any alpha already in the color string. It’s ignored when type is raw.type or value, it’s skipped and the role keeps its original color.
Raw colors
Raw colors are a flat map keyed by the selector name of a system color, such assystemBlueColor. Setting one replaces that color everywhere the app uses it, below the level of Discord’s roles. Semantic roles that point to a raw color with type: 'raw' all share its value.
Color formats
Every color string, whether inraw or in a semantic color entry, is parsed the same way. These formats are accepted:
hex
Six-digit hex. Alpha is treated as fully opaque.
hex
Eight-digit hex, with alpha as the final byte (
00 to FF).function
Three channels, each
0 to 255. Fully opaque.function
Three channels
0 to 255, plus alpha.- An
rgbastring with fewer than four values. - An
rgbstring with fewer than three values. - Any string that doesn’t start with
#,rgb, orrgba.
red are not supported.
A color that fails to parse doesn’t show an error. Unbound falls back to the original color for that entry and the app keeps rendering, so a malformed value looks like an override that didn’t apply. When a color doesn’t change, check its format first.