Theme System
LiquidBounce features a powerful theme system built on Svelte and served through an integrated browser backend. Themes control the appearance of menus, HUD, and all UI elements.
Overview
The theme system provides:
Svelte UI Modern reactive UI framework with component-based architecture
Multiple Sources Load themes from resources, local files, or marketplace
Custom Backgrounds Support for image backgrounds and GLSL shaders
Hot Reload Themes reload on resource manager refresh
Theme Manager
The ThemeManager (line 47) handles all theme operations:
object ThemeManager : Config ( "theme" ) {
internal val themesFolder = File (ConfigSystem.rootFolder, "themes" )
val themes: List < Theme >
val themeIds get () = themes. map { it.metadata.id }
private var currentTheme by text ( "Theme" , "liquidbounce" )
internal var includedTheme: Theme ? = null
private var temporaryTheme: Theme ? = null
}
Theme Origins
Themes can come from three sources, loaded in priority order:
Local Themes
Themes in .liquidbounce/themes/ directory (highest priority) Theme. load (Theme.Origin.LOCAL, file. relativeTo (themesFolder))
Marketplace Themes
Themes installed from marketplace MarketplaceManager. getSubscribedItemsOfType (MarketplaceItemType.THEME)
. forEach { item ->
Theme. load (Theme.Origin.MARKETPLACE, relativeFile)
}
Resource Theme
Built-in “liquidbounce” theme (fallback) includedTheme = Theme. load (Theme.Origin.RESOURCE, File ( "liquidbounce" ))
If multiple themes have the same ID, only the first (highest priority) is loaded.
Loading Themes
Themes are loaded during client initialization:
suspend fun init () {
// Load default theme
includedTheme = Theme. load (Theme.Origin.RESOURCE, File ( "liquidbounce" ))
}
suspend fun load () {
themes. clear ()
// Load local themes
themesFolder. listFiles { it.isDirectory }?. forEach { file ->
Theme. load (Theme.Origin.LOCAL, file. relativeTo (themesFolder))
. addIfUnloaded ()
}
// Load marketplace themes
MarketplaceManager. getSubscribedItemsOfType (MarketplaceItemType.THEME)
. forEach { item ->
Theme. load (Theme.Origin.MARKETPLACE, relativeFile)
. addIfUnloaded ()
}
// Update UI
ModuleHud. updateThemes ()
ScreenManager. update ()
}
Theme Selection
The active theme is determined by:
var theme: Theme ?
get () = temporaryTheme
?: themes. find { it.metadata.id. equals (currentTheme, true ) }
?: includedTheme
set ( value ) {
if ( value ?.origin?. external == true ) {
temporaryTheme = value // External theme
} else {
temporaryTheme = null
currentTheme = value .metadata.id
}
}
Temporary themes (external) take precedence but aren’t persisted to config.
Svelte Integration
Themes are built with Svelte and served through the browser backend:
Theme Structure
theme/
├── metadata.json # Theme information
├── src/
│ ├── App.svelte # Root component
│ ├── routes/ # Route components
│ │ ├── menu/
│ │ │ ├── title/Title.svelte
│ │ │ ├── multiplayer/Multiplayer.svelte
│ │ │ └── altmanager/AltManager.svelte
│ │ └── hud/
│ ├── integration/ # LiquidBounce integration
│ │ ├── ws.ts # WebSocket communication
│ │ ├── rest.ts # REST API
│ │ └── events.ts # Event handling
│ └── theme/
│ └── theme_config.ts
└── public/
└── img/ # Theme assets
Theme Configuration
Themes define their configuration in TypeScript:
// theme_config.ts
export let spaceSeperatedNames = writable ( false );
export function convertToSpacedString ( name : string ) : string {
const regex = / [ A-Z ] ? [ a-z ] + | [ 0-9 ] + | [ A-Z ] + (?! [ a-z ] ) / g ;
return ( name . match ( regex ) as string []). join ( " " );
}
Browser Integration
Themes are rendered in an embedded browser:
Opening a Theme Screen
fun openImmediate (
customScreenType: CustomScreenType ? = null ,
markAsStatic: Boolean = false ,
settings: BrowserSettings
): Browser {
val backend = BrowserBackendManager.backend
?: error ( "Browser backend is not initialized." )
return backend. createBrowser (
getScreenLocation (customScreenType, markAsStatic).url,
settings = settings
)
}
For interactive screens (menus, click GUI):
fun openInputAwareImmediate (
customScreenType: CustomScreenType ? = null ,
markAsStatic: Boolean = false ,
settings: BrowserSettings ,
priority: Short = 10 ,
inputAcceptor: InputAcceptor = takesInputHandler
): Browser
Input-aware screens run at higher refresh rates for smooth interaction.
Screen Routes
Themes define routes for different screens:
fun getScreenLocation (
customScreenType: CustomScreenType ? = null ,
markAsStatic: Boolean = false
): ScreenLocation {
val theme = theme. takeIf { theme ->
customScreenType == null ||
theme?. isSupported (customScreenType.routeName) == true
} ?: includedTheme
return ScreenLocation (
theme,
theme. getUrl (customScreenType?.routeName, markAsStatic)
)
}
Common routes include:
title - Main menu
multiplayer - Server list
altmanager - Account manager
hud - In-game HUD
clickgui - Click GUI
Backgrounds
Themes can provide custom backgrounds:
Background Images
fun loadBackgroundAsync (): CompletableFuture < Unit > = renderScope. future {
theme?. loadBackgroundImage ()
}
Shader Backgrounds
Themes can include GLSL shaders:
var shaderEnabled by boolean ( "Shader" , false )
. onChange { enabled ->
if (enabled) {
renderScope. launch {
theme?. compileShader ()
includedTheme?. compileShader ()
}
}
return @onChange enabled
}
Rendering backgrounds:
fun drawBackground (
context: GuiGraphics ,
width: Int , height: Int ,
mouseX: Int , mouseY: Int ,
delta: Float
): Boolean {
val background = if (shaderEnabled) {
theme?.backgroundShader
} else {
theme?.backgroundImage
} ?: return false
background. draw (context, width, height, mouseX, mouseY, delta)
return true
}
Resource Reloading
Themes support hot-reloading:
internal val reloader = ResourceManagerReloadListener { resourceManager ->
themes. forEach { it. onResourceManagerReload (resourceManager) }
logger. info ( "Reloaded ${ themes.size } themes." )
}
This is triggered when:
Resource packs are reloaded
F3+T is pressed
Themes are modified during development
Each theme includes a metadata.json file:
{
"id" : "liquidbounce" ,
"name" : "LiquidBounce Default" ,
"version" : "1.0.0" ,
"author" : "CCBlueX" ,
"description" : "Default LiquidBounce theme" ,
"routes" : [
"title" ,
"multiplayer" ,
"altmanager" ,
"hud" ,
"clickgui"
]
}
Creating Custom Themes
To create a custom theme:
Set Up Structure
Create a directory in .liquidbounce/themes/ with the required structure
Define Metadata
Create metadata.json with theme information and supported routes
Build UI
Create Svelte components for each route
Integrate with LiquidBounce
Use the integration modules (ws.ts, rest.ts, events.ts) to communicate with the client
Test and Load
Reload themes via resource reload or restart the client
Code References
ThemeManager.kt Theme management - line 47
integration/theme/ThemeManager.kt
src-theme/ Default theme source code
src-theme/src/
integration/ Svelte integration modules
src-theme/src/integration/
theme_config.ts Theme configuration
src-theme/src/theme/theme_config.ts
Best Practices
Support Core Routes
Ensure your theme supports at least title, multiplayer, and hud routes
Use Integration APIs
Leverage the provided WebSocket and REST APIs for client communication
Optimize Assets
Keep theme assets small for faster loading
Test Hot Reload
Verify your theme reloads correctly with F3+T
Handle Fallbacks
Gracefully handle missing data from the client