Create a custom theme
In this tutorial, you will set up the DIAL Chat Themes server with a custom color scheme, logo, and authentication screen colors. By the end, you will have a Docker Compose stack running DIAL Chat with your own branded theme.
Prerequisites
- Docker and Docker Compose V2 installed
- A text editor
- Basic familiarity with DIAL Chat layout concepts (see Theming and design)
What you will build
A self-contained Docker Compose stack with three services: DIAL Core, DIAL Chat, and a themes server. The themes server serves a config.json file that defines your custom color palette, logo images, and authentication page colors. DIAL Chat fetches this configuration at startup and applies it to the UI.
DIAL Chat ──THEMES_CONFIG_HOST──▶ Themes Server (/app/static/)
├── config.json
└── images/
Project structure
By the end of this tutorial, your project will look like this:
custom-theme/
├── docker-compose.yml
├── core/
│ └── config.json
└── themes/
└── static/
├── config.json
└── images/
└── logo.svg
Step 1: Create the project directory
Create the directory structure:
mkdir -p custom-theme/core custom-theme/themes/static/images
cd custom-theme
Verify: Run find . -type d and confirm you see ./core, ./themes/static, and ./themes/static/images.
Step 2: Create the DIAL Core configuration
Create custom-theme/core/config.json with a minimal configuration that includes an echo application for testing:
{
"routes": {},
"keys": {
"dial_api_key": {
"project": "test",
"role": "admin"
}
},
"applications": {
"echo": {
"endpoint": "http://echo:8080/openai/deployments/echo/chat/completions",
"displayName": "Echo",
"iconUrl": "message-square-lines-alt.svg"
}
},
"roles": {
"admin": {
"limits": {
"echo": {}
}
},
"default": {
"limits": {
"echo": {}
}
}
}
}
Step 3: Create the theme configuration
Create custom-theme/themes/static/config.json. This file defines your custom color palette. The themes array can hold multiple themes—the first one becomes the default for new users.
Below is a complete, working theme configuration with a dark color scheme. Every color token is required—do not remove or rename any of them:
{
"themes": [
{
"displayName": "My Dark Theme",
"id": "my-dark",
"colors": {
"bg-layer-0": "#0D1117",
"bg-layer-1": "#161B22",
"bg-layer-2": "#1C2333",
"bg-layer-3": "#242D3D",
"bg-layer-4": "#2D3748",
"bg-blackout": "#0D1117B3",
"bg-error": "#3D1F28",
"bg-warning": "#3D3B1F",
"bg-info": "#1C2C47",
"bg-success": "#1D3841",
"bg-accent-primary": "#4A90D9",
"bg-accent-secondary": "#2EA8A8",
"bg-accent-tertiary": "#9B6DD7",
"bg-accent-primary-alpha": "#4A90D92E",
"bg-accent-secondary-alpha": "#2EA8A82E",
"bg-accent-tertiary-alpha": "#9B6DD72E",
"bg-model-icon": "#FFFFFF",
"bg-overlay": "#0D1117B3",
"bg-neutral": "#242D3D",
"bg-inverted": "#E6EDF3",
"text-primary": "#E6EDF3",
"text-secondary": "#8B949E",
"text-error": "#F85149",
"text-warning": "#D4A72C",
"text-warning-icon": "#D4A72C",
"text-info": "#58A6FF",
"text-success": "#2EA8A8",
"text-accent-primary": "#58A6FF",
"text-accent-secondary": "#2EA8A8",
"text-accent-tertiary": "#9B6DD7",
"stroke-primary": "#6E7681",
"stroke-secondary": "#2D3748",
"stroke-tertiary": "#161B22",
"stroke-error": "#F85149",
"stroke-warning": "#D4A72C",
"stroke-info": "#58A6FF",
"stroke-success": "#2EA8A8",
"stroke-hover": "#E6EDF3",
"stroke-focus": "#E6EDF3",
"stroke-accent-primary": "#58A6FF",
"stroke-accent-secondary": "#2EA8A8",
"stroke-accent-tertiary": "#9B6DD7",
"controls-bg-accent": "#4A90D9",
"controls-bg-accent-hover": "#3D7EC4",
"controls-bg-accent-primary": "#2F6EB5",
"controls-bg-accent-primary-hover": "#3D7EC4",
"controls-bg-accent-primary-active": "#1A5BA0",
"controls-bg-accent-primary-alpha-active": "#58A6FF5C",
"controls-bg-accent-secondary-alpha-active": "#2EA8A85C",
"controls-bg-accent-tertiary-alpha-active": "#9B6DD75C",
"controls-bg-error": "#C93C37",
"controls-bg-error-hover": "#B53530",
"controls-bg-error-active": "#A02E2A",
"controls-bg-error-alpha-hover": "#F851492E",
"controls-bg-error-alpha-active": "#F851495C",
"controls-bg-disable-accent": "#6E7681",
"controls-bg-disable": "#2D3748",
"controls-bg-neutral-hover": "#2D3748",
"controls-bg-neutral-active": "#2D3748",
"controls-bg-accent-success-alpha-hover": "#2EA8A82E",
"controls-bg-accent-success-alpha-active": "#2EA8A85C",
"controls-text-permanent": "#F0F6FC",
"controls-text-accent-disable": "#2D3748",
"controls-text-primary-disable": "#6E7681",
"controls-text-secondary-disable": "#484F58",
"controls-text-neutral": "#F0F6FC",
"controls-text-accent-primary-hover": "#2F6EB5",
"controls-text-accent-primary-active": "#1A5BA0",
"controls-text-disable": "#484F58"
},
"topicColors": {
"bg-topic-default": "#4A90D92B",
"stroke-topic-default": "#4A90D9",
"bg-topic-business": "#4A90D92B",
"stroke-topic-business": "#4A90D9",
"bg-topic-development": "#56D13426",
"stroke-topic-development": "#56D134",
"bg-topic-user-experience": "#F04D7626",
"stroke-topic-user-experience": "#F04D76",
"bg-topic-analysis": "#9B6DD72B",
"stroke-topic-analysis": "#9B6DD7",
"bg-topic-sql": "#C08B5226",
"stroke-topic-sql": "#C08B52",
"bg-topic-sdlc": "#3D3B1F",
"stroke-topic-sdlc": "#D4A72C",
"bg-topic-talk-to-your-data": "#C400CB26",
"stroke-topic-talk-to-your-data": "#C400CB",
"bg-topic-rag": "#68882226",
"stroke-topic-rag": "#688822",
"bg-topic-text-generation": "#2EA8A82E",
"stroke-topic-text-generation": "#2EA8A8",
"bg-topic-image-generation": "#F0650026",
"stroke-topic-image-generation": "#F06500",
"bg-topic-image-recognition": "#4747F026",
"stroke-topic-image-recognition": "#4747F0"
},
"authColors": {
"bg-auth-layer-0": "#161B22",
"bg-auth-layer-1": "#242D3D"
}
}
],
"images": {
"default-model": "message-square-lines-alt.svg",
"default-addon": "message-square-lines-alt.svg",
"chat-logo-dark": "images/logo.svg",
"chat-logo-light": "images/logo.svg",
"chat-favicon": ""
}
}
Warning
Color token names must remain exactly as shown. Renaming or removing tokens breaks the mapping between design tokens and the DIAL Chat CSS.
The color tokens are organized into four groups:
bg-*— background colors for layers, status states, and accentstext-*— text colors for primary content, status messages, and accentsstroke-*— border and outline colorscontrols-*— interactive element colors (buttons, inputs, toggles)
The topicColors object defines colors for Catalog topic badges. The authColors object styles the sign-in page.
Step 4: Add a custom logo
Place your logo file at custom-theme/themes/static/images/logo.svg. For this tutorial, create a minimal SVG placeholder:
cat > themes/static/images/logo.svg << 'LOGOEOF'
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 24">
<text x="0" y="18" font-family="Arial, sans-serif" font-size="18" fill="#58A6FF">My DIAL</text>
</svg>
LOGOEOF
The themes server resolves relative paths in config.json against its own base URL. The "chat-logo-dark": "images/logo.svg" entry in the configuration tells DIAL Chat to load the logo from http://themes:8080/images/logo.svg.
Logo requirements:
- Maximum width: 100 px (as rendered in the Chat header)
- Any image format (SVG, PNG, JPG)
- Maximum file size: 512 MB
Step 5: Write the Docker Compose file
Create custom-theme/docker-compose.yml:
services:
themes:
image: epam/ai-dial-chat-themes:0.10.0
platform: linux/amd64
volumes:
- ./themes/static:/app/static
ports:
- "3001:8080"
chat:
image: epam/ai-dial-chat:0.41.7
platform: linux/amd64
environment:
THEMES_CONFIG_HOST: "http://themes:8080"
DIAL_API_HOST: "http://core:8080"
DIAL_API_KEY: "dial_api_key"
NEXTAUTH_SECRET: "secret"
ENABLED_FEATURES: "conversations-section,prompts-section,top-settings,top-clear-conversation,top-chat-info,top-chat-model-settings,empty-chat-settings,header,footer,custom-logo,marketplace"
ports:
- "3000:3000"
depends_on:
- themes
- core
core:
image: epam/ai-dial-core:0.39.2
platform: linux/amd64
environment:
AIDIAL_SETTINGS: "/opt/settings/settings.json"
aidial.config.files: '["/opt/config/config.json"]'
aidial.storage.overrides: '{ "jclouds.filesystem.basedir": "data" }'
ports:
- "8080:8080"
volumes:
- ./core:/opt/config
echo:
image: epam/ai-dial-core:0.39.2
platform: linux/amd64
entrypoint: ["java", "-cp", "/opt/service/*", "com.epam.aidial.core.server.EchoApplication"]
ports:
- "8081:8080"
Key configuration points:
- The volume mount
./themes/static:/app/staticreplaces the default theme files with your custom ones. THEMES_CONFIG_HOSTtells DIAL Chat where to fetchconfig.json.custom-logoinENABLED_FEATURESenables logo display in the header.depends_onensures the themes server starts before DIAL Chat.
Step 6: Start the services
docker compose up
Wait for all services to report as ready. You will see log lines like:
themes-1 | ... listening on 0.0.0.0:8080
chat-1 | ... ready on http://0.0.0.0:3000
core-1 | ... HTTP server started on port 8080
Verify: Open http://localhost:3001/config.json in your browser. You should see your custom theme configuration.
Step 7: Verify in DIAL Chat
- Open
http://localhost:3000in your browser. - Confirm your custom dark color scheme is applied — the background should use your
bg-layer-0color (#0D1117). - Check that your custom logo appears in the Chat header.
- Open the user settings (click the settings icon in the header). The theme picker should show "My Dark Theme."
Step 8: Add a second theme (optional)
To offer users a choice between themes, add a second object to the themes array in custom-theme/themes/static/config.json. Insert it after the closing } of the first theme, separated by a comma.
Here is a light theme that uses the same token structure with lighter colors:
{
"displayName": "My Light Theme",
"id": "my-light",
"colors": {
"bg-layer-0": "#FFFFFF",
"bg-layer-1": "#E8EEF5",
"bg-layer-2": "#F0F4F8",
"bg-layer-3": "#FFFFFF",
"bg-layer-4": "#D5DFEC",
"bg-blackout": "#0D11174D",
"bg-error": "#F5D5D7",
"bg-warning": "#FBF1CF",
"bg-info": "#D5E3FA",
"bg-success": "#D5F0F1",
"bg-accent-primary": "#2F6EB5",
"bg-accent-secondary": "#1A9090",
"bg-accent-tertiary": "#7B4FBF",
"bg-accent-primary-alpha": "#58A6FF2E",
"bg-accent-secondary-alpha": "#2EA8A82E",
"bg-accent-tertiary-alpha": "#9B6DD72E",
"bg-overlay": "#FFFFFF4D",
"bg-neutral": "#FFFFFF",
"bg-inverted": "#1C2333",
"text-primary": "#1C2333",
"text-secondary": "#484F58",
"text-error": "#A02E2A",
"text-warning": "#7A5E00",
"text-warning-icon": "#D4A72C",
"text-info": "#1A5BA0",
"text-success": "#1A9090",
"text-accent-primary": "#1A5BA0",
"text-accent-secondary": "#1A9090",
"text-accent-tertiary": "#7B4FBF",
"stroke-primary": "#6E7681",
"stroke-secondary": "#D5DFEC",
"stroke-tertiary": "#E8EEF5",
"stroke-hover": "#1C2333",
"stroke-focus": "#1C2333",
"stroke-error": "#A02E2A",
"stroke-warning": "#D4A72C",
"stroke-info": "#1A5BA0",
"stroke-success": "#1A9090",
"stroke-accent-primary": "#1A5BA0",
"stroke-accent-secondary": "#1A9090",
"stroke-accent-tertiary": "#7B4FBF",
"controls-bg-accent": "#4A90D9",
"controls-bg-accent-hover": "#3D7EC4",
"controls-bg-accent-primary": "#1A5BA0",
"controls-bg-accent-primary-hover": "#2F6EB5",
"controls-bg-accent-primary-active": "#3D7EC4",
"controls-bg-accent-primary-alpha-active": "#58A6FF5C",
"controls-bg-accent-secondary-alpha-active": "#2EA8A85C",
"controls-bg-accent-tertiary-alpha-active": "#9B6DD75C",
"controls-bg-error": "#A02E2A",
"controls-bg-error-hover": "#B53530",
"controls-bg-error-active": "#C93C37",
"controls-bg-error-alpha-hover": "#F851492E",
"controls-bg-error-alpha-active": "#F851495C",
"controls-bg-disable": "#D5DFEC",
"controls-bg-disable-accent": "#6E7681",
"controls-bg-neutral-hover": "#D5DFEC",
"controls-bg-neutral-active": "#8B949E",
"controls-bg-accent-success-alpha-hover": "#2EA8A82E",
"controls-bg-accent-success-alpha-active": "#2EA8A85C",
"controls-text-permanent": "#F0F6FC",
"controls-text-accent-primary-hover": "#3D7EC4",
"controls-text-accent-primary-active": "#58A6FF",
"controls-text-neutral": "#1C2333",
"controls-text-accent-disable": "#D5DFEC",
"controls-text-primary-disable": "#6E7681",
"controls-text-secondary-disable": "#8B949E",
"controls-text-disable": "#8B949E"
},
"topicColors": {
"bg-topic-default": "#4A90D92B",
"stroke-topic-default": "#4A90D9",
"bg-topic-business": "#4A90D92B",
"stroke-topic-business": "#4A90D9",
"bg-topic-development": "#56D13426",
"stroke-topic-development": "#56D134",
"bg-topic-user-experience": "#F04D7626",
"stroke-topic-user-experience": "#F04D76",
"bg-topic-analysis": "#9B6DD72B",
"stroke-topic-analysis": "#9B6DD7",
"bg-topic-sql": "#C08B5226",
"stroke-topic-sql": "#C08B52",
"bg-topic-sdlc": "#FBF1CF",
"stroke-topic-sdlc": "#D4A72C",
"bg-topic-talk-to-your-data": "#C400CB26",
"stroke-topic-talk-to-your-data": "#C400CB",
"bg-topic-rag": "#68882226",
"stroke-topic-rag": "#688822",
"bg-topic-text-generation": "#2EA8A82E",
"stroke-topic-text-generation": "#2EA8A8",
"bg-topic-image-generation": "#F0650026",
"stroke-topic-image-generation": "#F06500",
"bg-topic-image-recognition": "#4747F026",
"stroke-topic-image-recognition": "#4747F0"
},
"authColors": {
"bg-auth-layer-0": "#E8EEF5",
"bg-auth-layer-1": "#FFFFFF"
}
}
After saving the file, restart the services:
docker compose restart chat
Note
After making changes to theme files, restart DIAL Chat to apply them. Theme changes do not take effect dynamically.
Verify: Open DIAL Chat and navigate to user settings. The theme picker should now show both "My Dark Theme" and "My Light Theme." Switching between them changes the color scheme.
What you learned
- How to deploy the DIAL Chat Themes server with Docker Compose
- How to override default theme files using a Docker volume mount
- The structure of
config.jsonand the color token system (background, text, stroke, controls) - How to add a custom logo referenced by relative path
- How to offer multiple themes via the
themesarray
Further reading
- Theming and design — layout customization, overlay mode, and the color token system in detail
- Chat localization — customize and translate UI text
- DIAL Chat configuration — complete environment variable reference for Chat
- DIAL Chat Themes — the static service that holds Chat's branding