Theming
Void Video uses a JSON-driven theming engine for the on-screen UI. Themes control colors, fonts, widget styles, and per-layer transparency — everything rendered by the Nuklear-based menu and OSD system.
Theme Files
Themes are loaded from themes/<name>.json relative to the application directory. The active theme is set in your config:
{
"ui": {
"theme_name": "void_dark"
}
}If the JSON file can't be found, the built-in void_dark defaults are used.
File Format
Theme files are JSON with support for // single-line comments. The top-level structure:
{
"name": "my_theme",
"fonts": { ... },
"base_colors": { ... },
"semantic_colors": { ... },
"style_overrides": { ... },
"widget_styles": { ... },
"layer_overrides": { ... }
}All sections are optional — omitted sections use the built-in defaults.
Fonts
Custom TTF fonts can be loaded from the theme directory. Each font has a name, a path to a .ttf file (relative to the theme JSON), and a pixel size.
"fonts": {
"body": { "path": "fonts/DMSans.ttf", "size": 18 },
"heading": { "path": "fonts/DMSans.ttf", "size": 24 },
"mono": { "path": "fonts/JetBrainsMono-Regular.ttf", "size": 14 }
}The font named body is used as the default UI font. If no body font is defined, the first font in the list is used. If no fonts are defined at all, Nuklear's built-in default font is used.
Fonts are baked into a texture atlas on the GPU. All defined fonts share a single atlas, so adding many fonts or very large sizes will increase GPU memory usage.
TIP
Place your .ttf files in a fonts/ subdirectory next to your theme JSON for clean organization.
Base Colors
The 28 base colors that Nuklear derives its entire widget style tree from. Each is an RGBA array [r, g, b, a] with values 0–255.
"base_colors": {
"text": [240, 242, 248, 255],
"window": [12, 14, 20, 200],
"header": [16, 18, 26, 220],
"border": [255, 255, 255, 15],
"button": [20, 22, 30, 255],
"button_hover": [30, 42, 60, 255],
"button_active": [25, 60, 90, 255],
"toggle": [20, 22, 30, 255],
"toggle_hover": [30, 42, 60, 255],
"toggle_cursor": [79, 195, 247, 255],
"select": [12, 14, 20, 255],
"select_active": [20, 40, 60, 180],
"slider": [20, 22, 30, 255],
"slider_cursor": [79, 195, 247, 200],
"slider_cursor_hover": [128, 220, 255, 220],
"slider_cursor_active": [192, 238, 255, 255],
"property": [16, 18, 26, 255],
"edit": [16, 18, 26, 255],
"edit_cursor": [79, 195, 247, 255],
"combo": [20, 22, 30, 255],
"chart": [20, 22, 30, 255],
"chart_color": [79, 195, 247, 180],
"chart_color_highlight": [124, 77, 255, 255],
"scrollbar": [12, 14, 20, 255],
"scrollbar_cursor": [40, 44, 58, 255],
"scrollbar_cursor_hover": [60, 68, 90, 255],
"scrollbar_cursor_active": [79, 195, 247, 180],
"tab_header": [16, 18, 26, 220]
}Semantic Colors
Named colors used by the application for specific UI elements. These are independent of Nuklear's widget system and accessed in code via theme->semantic(SemanticColor::X).
| Key | Purpose | Default (void_dark) |
|---|---|---|
osd_section_header | OSD section headings | Cyan #4fc3f7 |
osd_latency | Latency display | Gold #f4a842 |
osd_warning | Warning messages | Gold #f4a842 |
osd_error | Error messages | Red |
osd_exposure_header | Exposure section heading | Green #80e0a0 |
ev_positive | Positive EV values | Green #80e0a0 |
ev_negative | Negative EV values | Gold #f4a842 |
ev_graph_line | EV graph line color | Cyan #4fc3f7 |
scene_change_spike | Scene change spike marker | Purple #7c4dff |
scene_change_text | Scene change label | Purple dimmed |
menu_selected_bg | Selected menu item background | Cyan glow |
menu_selected_text | Selected menu item text | White |
menu_active_bg | Active (current value) background | Subtle cyan |
menu_active_text | Active (current value) text | Cyan #4fc3f7 |
histogram_bar | Histogram bar color | Cyan #4fc3f7 |
no_signal | No signal indicator | Red |
"semantic_colors": {
"osd_section_header": [79, 195, 247, 255],
"menu_selected_bg": [79, 195, 247, 40],
"menu_selected_text": [240, 242, 248, 255],
"scene_change_spike": [124, 77, 255, 255]
}Style Overrides
Numeric properties for widget geometry — border width, rounding, padding, and spacing.
"style_overrides": {
"window": {
"border": 1.0,
"rounding": 0.0,
"padding": [10, 10],
"group_padding": [6, 6],
"group_border": 1.0,
"spacing": [4, 4],
"scrollbar_size": [8, 8]
},
"button": {
"border": 1.0,
"rounding": 2.0,
"padding": [6, 4]
},
"selectable": {
"rounding": 0.0,
"padding": [6, 3]
},
"chart": {
"border": 0.0,
"rounding": 0.0,
"padding": [4, 4]
},
"scrollbar": {
"border": 0.0,
"rounding": 3.0,
"rounding_cursor": 3.0
}
}Widget Styles
Per-widget background style items for each interaction state (normal, hover, active). Each state supports multiple types:
Color
Flat solid color:
{ "type": "color", "value": [79, 195, 247, 40] }Gradient
Vertical gradient rendered as a GPU texture:
{ "type": "gradient", "top": [20, 22, 30, 255], "bottom": [12, 14, 20, 255] }Image
PNG or JPEG texture loaded from a file path (relative to the theme JSON):
{ "type": "image", "path": "button_bg.png" }Nine-Slice
Stretchable image with fixed border regions:
{ "type": "nine_slice", "path": "panel.png", "l": 8, "t": 8, "r": 8, "b": 8 }Hidden
Fully transparent / invisible:
{ "type": "hidden" }Widget Paths
The widget_styles section uses dot-separated paths to target specific widgets:
| Path | Widget | States |
|---|---|---|
button | Standard button | normal, hover, active |
contextual_button | Context menu button | normal, hover, active |
menu_button | Menu bar button | normal, hover, active |
selectable | Selectable item | normal, hover, pressed |
selectable.active | Selectable (active) | normal_active, hover_active, pressed_active |
toggle / checkbox | Checkbox | normal, hover, active |
checkbox.cursor | Checkbox check mark | cursor_normal, cursor_hover |
option | Radio button | normal, hover, active |
slider | Slider track | normal, hover, active |
slider.cursor | Slider handle | cursor_normal, cursor_hover, cursor_active |
progress | Progress bar track | normal, hover, active |
progress.cursor | Progress bar fill | cursor_normal, cursor_hover, cursor_active |
scrollbar | Scrollbar track | normal, hover, active |
scrollbar.cursor | Scrollbar thumb | cursor_normal, cursor_hover, cursor_active |
edit | Text edit field | normal, hover, active |
property | Property field | normal, hover, active |
combo | Combo box | normal, hover, active |
tab | Tab header | normal |
window | Window background | normal |
window.header | Window title bar | normal, hover, active |
window.scaler | Window resize handle | normal |
Example:
"widget_styles": {
"selectable": {
"normal": { "type": "color", "value": [12, 14, 20, 0] },
"hover": { "type": "color", "value": [79, 195, 247, 25] },
"pressed": { "type": "color", "value": [79, 195, 247, 40] }
},
"slider.cursor": {
"cursor_normal": { "type": "color", "value": [79, 195, 247, 200] },
"cursor_hover": { "type": "color", "value": [128, 220, 255, 230] },
"cursor_active": { "type": "color", "value": [192, 238, 255, 255] }
}
}Layer Overrides
Different UI layers (OSD, Menu, Status bar) can have independent alpha values for window backgrounds and borders. This lets the OSD be more transparent than the menu system.
"layer_overrides": {
"OSD": { "window_alpha": 180, "border_alpha": 100 },
"Menu": { "window_alpha": 210, "border_alpha": 150 },
"Status": { "window_alpha": 180, "border_alpha": 120 }
}Values are 0–255, where 255 is fully opaque.
Creating a Custom Theme
- Copy
themes/void_dark.jsonas a starting point - Rename and edit the colors to match your preference
- Set
theme_namein your config to your new theme name (without.json) - Restart Void Video
TIP
You only need to include the sections you want to change. Omitted sections inherit from the built-in defaults.