Styling Your LLMClient’s Chat
Every LLMClient comes with a chat window: in the Smarter workbench, where you test it, and on every web page that embeds it with Smarter Chat. Out of the box, that window is a tidy blue and white. With a few lines in your manifest, it can wear your brand: your logo in the corner, your colors on the message bubbles, your font, a picture behind the conversation, and a friendly face beside each of the assistant’s answers.
This page is written for prompt engineers. You don’t need to be a web developer, and you don’t need to know CSS (the language that web pages use for their looks). Everything you need is explained here, with examples you can copy.
Where the Styles Go
An LLMClient’s manifest has two parts under spec.config:
prompt(required) is what the LLMClient is: its LLM provider and model, its system prompt, and the words of its chat, such as its name, welcome message, example prompts and placeholder.styles(optional) is how its chat looks.
apiVersion: smarter.sh/v1
kind: LLMClient
metadata:
name: stackademy_sql
description: Stackademy's course advisor.
version: 1.0.0
spec:
config:
prompt:
provider: openai
defaultModel: gpt-6-luna
appName: Stackademy
appAssistant: Stanley
appWelcomeMessage: Welcome to Stackademy! How can I help you today?
styles:
appLogoUrl: https://cdn.example.com/stackademy/logo.png
outgoingMessage:
backgroundColor: "#fca311"
That’s a complete styling: a logo, and the user’s messages in Stackademy’s amber. Every setting in
styles is optional. Anything you leave out keeps its default look, so you can start with one line
and add more as you go.
Apply the manifest as usual, then reload the LLMClient’s chat in the workbench to see the result:
smarter apply -f stackademy.yaml
The Parts of the Chat
The chat window is made of a few parts, and styles has a section for each. The section names are
the names that the chat’s building blocks use (see Under the Hood), so they’re the same ones you’ll
see in the chat’s technical documentation.
mainContainerthe frame around everything
conversationHeaderthe dark bar at the top, with appLogoUrl in its circle
messageListthe striped area that holds the conversation, and appBackgroundImageUrl
incomingMessagethe assistant's gray bubble, with assistantAvatarUrl beside it
outgoingMessagethe user's amber bubble, with userAvatarUrl beside it
typingIndicator"Stanley is typing", while the LLM answers
messageInputthe box at the bottom, where the user types
buttonsthe send arrow, the attach button, and the header's buttons
A Two-Minute CSS Crash Course
Styles are written as CSS values, the same values that every web page uses. You only need four kinds.
Colors
There are three ways to write a color. Use whichever your brand guide gives you.
Kind |
Example |
Notes |
|---|---|---|
Hex code |
|
The most common. A |
Color name |
|
Browsers know about 140 color names, such as |
|
|
Red, green and blue from 0 to 255. The |
Any color picker shows a color’s hex code. Try Google’s color picker, or your design tool’s.
Sizes
A size is a number and a unit, with no space between them:
px(pixels) is a fixed size:16pxis a typical text size.emandremare relative to the text size:1emis one text-height,0.5emhalf of one. Sizes inemgrow and shrink with the text.0needs no unit.
Fonts
A font stack is a list of fonts, separated by commas. The visitor’s browser uses the first one that it
has, so a stack ends with a generic family: sans-serif, serif or monospace.
fontFamily: '"Georgia", "Times New Roman", serif'
Most visitors don’t have your brand font installed, so the chat can download one for them, from the free Google Fonts catalog. Find a font there, and copy its name exactly:
googleFont: Nunito
That’s all: when fontFamily isn’t set, the chat uses the Google font, with a sensible stack behind it.
Images
An image is a web address (url) that starts with https:// and ends at the image itself, usually
.png, .jpg, .svg or .webp. Paste it into a browser’s address bar: if you see the image and
nothing else, it’s the right url. A link to a page that shows the image, such as a Google Drive or
Dropbox sharing page, won’t work. Host images somewhere public, such as your website, a CDN, or a public
bucket.
Logo and avatars are shown in small circles, about 42 pixels wide. Use a square image, at least 128 by 128 pixels, with its subject in the middle. A logo is fitted whole into a white circle. An avatar fills its circle, so its corners are cut off.
Background images sit behind the conversation. Choose a quiet image or a light pattern, so that the messages stay easy to read, and keep it under about 500 KB, so that the chat loads quickly.
When you apply the manifest, Smarter opens each image url, as a browser would, and refuses the manifest if one doesn’t answer with an image: a typo, an image that hasn’t been uploaded yet, a private file, or a link to a web page instead of the image itself. A url that forwards to the image (a redirect) is fine.
If an image disappears later, after it was applied, the chat leaves it out rather than show a broken picture. It just won’t show your image.
Every Setting
All settings are optional.
The overall look
Setting |
What it does |
Example |
|---|---|---|
|
Your logo, in a circle at the left of the header. It’s also shown on the LLMClient’s card in the web console. |
|
|
A picture behind the conversation. See Background Images. |
|
|
How the picture fills the area: |
|
|
A small picture beside each of the assistant’s messages: a mascot, a face, or your logo. |
|
|
A small picture beside each of the user’s messages. |
|
|
A font from Google Fonts, which the chat downloads. |
|
|
The chat’s font stack. It wins over |
|
|
The size of the chat’s text. Every other size grows and shrinks with it. The default is |
|
|
How round the corners of the message bubbles and of the message box are. |
|
The parts of the chat
Each part is a section of styles, with its settings indented beneath it:
styles:
conversationHeader:
backgroundColor: "#14213d"
color: "#ffffff"
Section |
Settings |
What they color |
|---|---|---|
|
|
The frame around the whole chat, its default text color, and the thin line around it. |
|
|
The bar at the top: its background, the LLMClient’s name ( |
|
|
The area that holds the conversation. Over a background image, |
|
|
The assistant’s bubbles: their color, their text, an optional thin line around them, and the links in them. |
|
|
The user’s bubbles, likewise. |
|
|
The strip at the bottom ( |
|
|
The send and attach buttons, and the header’s buttons. |
|
|
“Stanley is typing” and its bouncing dots. |
Background Images
A background image sits behind the conversation, and messageList.backgroundColor is laid over it like
tinted glass. A solid color would hide the image completely, so use an rgba color with an opacity
below 1:
styles:
appBackgroundImageUrl: https://cdn.example.com/stackademy/pattern.png
appBackgroundImageSize: auto # a pattern, repeated like tiles
messageList:
backgroundColor: rgba(255, 255, 255, 0.85) # 85% white, over the pattern
The higher the opacity, the fainter the image, and the easier the messages are to read. Between 0.75
and 0.9 usually looks good. For a dark chat, tint with a dark color instead, such as
rgba(20, 33, 61, 0.9).
Case Study: Stackademy
Stackademy is an online coding school, the fictional company behind several of Smarter’s examples. Its
course advisor, an LLMClient named stackademy_sql, answers questions about the course catalog. It works
well, but on Stackademy’s website it looks like every other chat. The marketing team sends over the brand
guide:
Colors: Oxford navy
#14213d, amber#fca311, platinum gray#e5e5e5, and white.Font: Nunito, a rounded, friendly sans-serif.
Logo: a square PNG, and a mascot, Stanley, the friendly owl who answers students’ questions.
Tone: welcoming, a little playful, never childish.
Here’s how a prompt engineer turns that into styles, one step at a time, applying the manifest after
each step and reloading the workbench to see the result.
Step 1: put the images online
The logo, Stanley’s portrait and a faint pattern of code symbols go on Stackademy’s CDN. Each is checked by pasting its url into a browser.
Step 2: the header and the logo
The header is the first thing that visitors see, so it gets the boldest color: navy, with white text, and the model’s name in amber.
styles:
appLogoUrl: https://cdn.stackademy.edu/brand/logo.png
conversationHeader:
backgroundColor: "#14213d"
color: "#ffffff"
infoColor: "#fca311"
Step 3: the bubbles
Students’ own messages are amber, and Stanley’s are platinum gray, both with navy text. Navy on amber is easy to read; white on amber, the first idea, was too faint. Links in Stanley’s answers are a clear blue, and Stanley’s portrait goes beside them.
assistantAvatarUrl: https://cdn.stackademy.edu/brand/stanley.png
incomingMessage:
backgroundColor: "#e5e5e5"
color: "#14213d"
linkColor: "#0b57d0"
outgoingMessage:
backgroundColor: "#fca311"
color: "#14213d"
Step 4: the font, the corners, and the background
Nunito comes from Google Fonts. Rounder corners suit its friendly letters. The code-symbol pattern is tiled behind the conversation, and tinted 85% white so that it whispers rather than shouts.
googleFont: Nunito
fontSize: 15px
borderRadius: 16px
appBackgroundImageUrl: https://cdn.stackademy.edu/brand/pattern.png
appBackgroundImageSize: auto
messageList:
backgroundColor: rgba(255, 255, 255, 0.85)
color: "#14213d"
Step 5: the finishing touches
The send button and the typing indicator pick up the amber, and the message box is a soft gray, so that it reads as a place to type.
messageInput:
editorBackgroundColor: "#f1f1f1"
placeholderColor: "#6c757d"
buttons:
color: "#fca311"
typingIndicator:
color: "#fca311"
Step 6: check, and ship
Before deploying, the prompt engineer checks three things:
Can everyone read it? Each text color is checked against its background with WebAIM’s free contrast checker. Aim for its “WCAG AA: Pass”.
Does it work on a phone? The workbench is resized to a phone’s width.
Does it survive a long answer? A question that produces a table, a code block and a list.
Then the LLMClient is deployed, and the very same look appears on Stackademy’s website, which embeds it with Smarter Chat. No web developer was needed: the next time marketing changes a color, the prompt engineer changes one line and applies the manifest again.
The complete manifest is one of Smarter’s built-in examples. In it, Stackademy’s images are on Smarter’s own CDN, rather than Stackademy’s:
apiVersion: smarter.sh/v1
kind: LLMClient
metadata:
name: stackademy_sql
description: Stackademy University course catalogue inquiries using the Stackademy SQL plugin.
version: 1.0.0
tags:
- stackademy
- sql
- llmclient
annotations:
- smarter.sh/llm-client/stackademy-sql/creator: QA Team
- smarter.sh/llm-client/stackademy-sql/purpose: llmclient for Stackademy course catalogue inquiries
- smarter.sh/llm-client/stackademy-sql/release-date: 2024-06-15
- smarter.sh/llm-client/stackademy-sql/backward-compatibility: true
- smarter.sh/llm-client/stackademy-sql/note: |
Uses SQL plugin to answer course catalogue questions.
Very fast and accurate when the database is well-structured.
Leverages cache for improved performance.
spec:
config:
prompt:
deployed: false
provider: openai
defaultModel: gpt-6-luna
defaultSystemRole: >
You are a helpful assistant. When given the opportunity to utilize
function calling, you should always do so. This will allow you to
provide the best possible responses to the user. DO NOT GUESS. IF
YOU DON'T KNOW THE ANSWER, RESPOND THAT YOU DON'T KNOW.
defaultTemperature: 1.0
defaultMaxTokens: 1024
appName: Stackademy
appAssistant: Stanley
appWelcomeMessage: Welcome to Stackademy! How can I help you today?
appExamplePrompts:
- "Do you offer any courses on AI?"
- "My budget is $1,000. What courses can I take?"
- "I want to study programming. What do you suggest?"
appPlaceholder: "Ask me anything about Stackademy courses..."
appInfoUrl: https://stackademy.edu/online-courses
styles: # Stackademy's colors
# appLogoUrl: https://cdn.smarter.sh/images/llmclients/stackademy/logo.png # coming soon: upload, then uncomment
# appBackgroundImageUrl: https://cdn.smarter.sh/images/llmclients/stackademy/background.png # coming soon: upload, then uncomment
# assistantAvatarUrl: https://cdn.smarter.sh/images/llmclients/stackademy/assistant.png # coming soon: upload, then uncomment
googleFont: Nunito
fontSize: 15px
borderRadius: 16px
conversationHeader:
backgroundColor: '#14213d'
color: '#ffffff'
infoColor: '#fca311'
messageList:
backgroundColor: rgba(255, 255, 255, 0.85)
color: '#14213d'
incomingMessage:
backgroundColor: '#e5e5e5'
color: '#14213d'
linkColor: '#0b57d0'
outgoingMessage:
backgroundColor: '#fca311'
color: '#14213d'
messageInput:
backgroundColor: '#ffffff'
editorBackgroundColor: '#f1f1f1'
placeholderColor: '#6c757d'
buttons:
color: '#fca311'
typingIndicator:
color: '#fca311'
plugins:
- stackademy_sql
functions: []
More Examples
Every built-in LLMClient has styles, from a single color to a full theme, so each is an example to copy. They’re in the Smarter repository, under smarter/smarter/apps/llmclient/data/llm-clients/:
llmclient-simple.yaml: just a logo and one color. A little goes a long way.llmclient-mcp-builder.yaml: a dark theme, like a code editor’s, in a monospace font.llmclient-math.yaml: a chalkboard, with a handwriting font and a wooden frame.llmclient-weather.yaml: a sunny sky, with round, puffy bubbles.llmclient-research-analyst.yaml: a library’s reading room, in a serif font.llmclient-smarter.yaml: Smarter’s own orange and gold.
Troubleshooting
Apply says a value “is not valid”. The error names the setting and shows the value. Check its form
against A Two-Minute CSS Crash Course: a color needs its #, a size needs its unit (16px, not
16), and an image url must start with https:// (not http://).
Apply says an image “is not available”. Smarter couldn’t open the image. Paste the url into a
private browser window: if you don’t see the image, nobody else will either. HTTP 403 or HTTP 404
usually means a typo, or a file that isn’t uploaded or isn’t public yet.
Apply says an image is “a text/html page, not an image”. The url is of a page that shows the image, such as a sharing link. Right-click the image, choose Copy Image Address, and use that url.
Apply says the manifest “has keys that it does not define”. A setting’s name is misspelled, or is in
the wrong section. Names are case-sensitive: backgroundColor, not backgroundcolor or
background_color.
Apply says that spec.config “is split into prompt … and styles”. The manifest was written before
spec.config had its prompt and styles sections. Indent its settings under prompt:, and move
appLogoUrl and appBackgroundImageUrl to styles:, as the error lists.
A hex color disappeared. In YAML, # starts a comment, so backgroundColor: #fca311 is an empty
setting. Quote it: backgroundColor: "#fca311".
The font didn’t change. Check the name’s spelling against Google Fonts,
including its capitals and spaces (Source Sans 3). If you also set fontFamily, it wins.
The background image doesn’t show. messageList.backgroundColor may be solid. Give it an opacity
below 1, such as rgba(255, 255, 255, 0.85).
The logo or avatar doesn’t show. The image was available when you applied the manifest, but isn’t now. Paste its url into a browser to check it.
Under the Hood
This section is for the curious, and for web developers.
Smarter Chat is built on chatscope’s open source chat components,
@chatscope/chat-ui-kit-react, and their theme,
@chatscope/chat-ui-kit-styles. styles follows
the theme’s design closely: its sections are chatscope’s components (MainContainer,
ConversationHeader, MessageList, Message, MessageInput, Button, TypingIndicator and
Avatar), and each setting is one of the theme’s variables, such as $conversation-header-bg-color
for conversationHeader.backgroundColor. Each setting’s description in the
Pydantic models names its variable.
chatscope’s theme is compiled ahead of time, so Smarter Chat can’t recompile it for each LLMClient.
Instead, the config api returns the styles (with snake_case keys, as llmclient.styles), and the chat
sets each one as a CSS custom property on its root element, .SmarterChat. Its stylesheet applies
the properties to chatscope’s elements, with chatscope’s own value as each one’s fallback:
.SmarterChat .cs-conversation-header {
background-color: var(--smarter-chat-header-bg, #f6fbff);
}
The properties are named --smarter-chat-<part>-<setting>, for example --smarter-chat-header-bg,
--smarter-chat-incoming-link-color and --smarter-chat-input-placeholder-color, plus
--smarter-chat-font-family, --smarter-chat-font-size and --smarter-chat-border-radius. A page
that embeds the chat can set them itself, on .SmarterChat in its own stylesheet, and anything beyond
them can be styled with chatscope’s cs- classes, and Smarter Chat’s smarter-chat- classes.
Because styles are written into every page that embeds the LLMClient, Smarter accepts only plain values:
colors, sizes and font names that can’t contain anything else, and https image urls. Apply rejects
any other value, and the chat ignores one that it receives. Apply also requests each image url, with
the same protections as plugins’ web requests (public addresses only, validated redirects, timeouts),
and requires HTTP 200 and a Content-Type that isn’t a web page.