Skip to main content

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 accents
  • text-* — text colors for primary content, status messages, and accents
  • stroke-* — border and outline colors
  • controls-* — interactive element colors (buttons, inputs, toggles)

The topicColors object defines colors for Catalog topic badges. The authColors object styles the sign-in page.

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/static replaces the default theme files with your custom ones.
  • THEMES_CONFIG_HOST tells DIAL Chat where to fetch config.json.
  • custom-logo in ENABLED_FEATURES enables logo display in the header.
  • depends_on ensures 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​

  1. Open http://localhost:3000 in your browser.
  2. Confirm your custom dark color scheme is applied — the background should use your bg-layer-0 color (#0D1117).
  3. Check that your custom logo appears in the Chat header.
  4. 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.json and 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 themes array

Further reading​