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 :doc:`Smarter Chat <../../smarter-framework/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.
.. contents:: On this page
:local:
:depth: 1
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*.
.. code-block:: yaml
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:
.. code-block:: console
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.
.. raw:: html
S
Stackademy v1.0.0
openai gpt-6-luna
⭯
Welcome to Stackademy! How can I help you today?
Do you offer any courses on AI?
••• Stanley is typing
Ask me anything about Stackademy courses...
➤
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.
.. list-table::
:header-rows: 1
:widths: 30 30 40
* - Kind
- Example
- Notes
* - Hex code
- ``"#fca311"``
- The most common. A ``#`` and six digits or letters (0-9, a-f): two each for red, green and
blue. ``"#ffffff"`` is white, ``"#000000"`` is black. Short forms like ``"#fff"`` work too.
**Put hex colors in quotes**, because in YAML a ``#`` starts a comment.
* - Color name
- ``tomato``
- Browsers know about 140 color names, such as ``navy``, ``gold``, ``white`` and ``tomato``. See
`the list of names `__.
* - ``rgb`` / ``rgba``
- ``rgba(255, 255, 255, 0.8)``
- Red, green and blue from 0 to 255. The ``a`` version adds *opacity* from 0 (invisible) to 1
(solid), so ``0.8`` is 80% solid, and lets what's behind it show through a little.
``hsl()`` and ``hsla()`` colors work too.
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: ``16px`` is a typical text size.
- ``em`` and ``rem`` are relative to the text size: ``1em`` is one text-height, ``0.5em`` half of one.
Sizes in ``em`` grow and shrink with the text.
- ``0`` needs 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``.
.. code-block:: yaml
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:
.. code-block:: yaml
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
~~~~~~~~~~~~~~~~
.. list-table::
:header-rows: 1
:widths: 28 47 25
* - Setting
- What it does
- Example
* - ``appLogoUrl``
- Your logo, in a circle at the left of the header. It's also shown on the LLMClient's card in
the web console.
- ``https://cdn.example.com/logo.png``
* - ``appBackgroundImageUrl``
- A picture behind the conversation. See `Background Images`_.
- ``https://cdn.example.com/bg.png``
* - ``appBackgroundImageSize``
- How the picture fills the area: ``cover`` (the default) fills it and crops the picture's edges,
``contain`` shows the whole picture, and ``auto`` repeats it at its own size, like tiles. Use
``auto`` for a pattern.
- ``auto``
* - ``assistantAvatarUrl``
- A small picture beside each of the assistant's messages: a mascot, a face, or your logo.
- ``https://cdn.example.com/stanley.png``
* - ``userAvatarUrl``
- A small picture beside each of the user's messages.
- ``https://cdn.example.com/user.png``
* - ``googleFont``
- A font from `Google Fonts `__, which the chat downloads.
- ``Nunito``
* - ``fontFamily``
- The chat's font stack. It wins over ``googleFont``, so use it to put a Google font in a stack of
your own.
- ``'"Nunito", Arial, sans-serif'``
* - ``fontSize``
- The size of the chat's text. Every other size grows and shrinks with it. The default is ``16px``.
- ``15px``
* - ``borderRadius``
- How round the corners of the message bubbles and of the message box are. ``0`` is square, and the
default is ``0.7em``.
- ``16px``
The parts of the chat
~~~~~~~~~~~~~~~~~~~~~
Each part is a section of ``styles``, with its settings indented beneath it:
.. code-block:: yaml
styles:
conversationHeader:
backgroundColor: "#14213d"
color: "#ffffff"
.. list-table::
:header-rows: 1
:widths: 24 30 46
* - Section
- Settings
- What they color
* - ``mainContainer``
- ``backgroundColor``, ``color``, ``borderColor``
- The frame around the whole chat, its default text color, and the thin line around it.
* - ``conversationHeader``
- ``backgroundColor``, ``color``, ``infoColor``, ``borderColor``
- The bar at the top: its background, the LLMClient's name (``color``), the line beneath the name,
which names the LLM provider and model (``infoColor``), and the line under the bar.
* - ``messageList``
- ``backgroundColor``, ``color``
- The area that holds the conversation. Over a background image, ``backgroundColor`` tints the
image. See `Background Images`_.
* - ``incomingMessage``
- ``backgroundColor``, ``color``, ``borderColor``, ``linkColor``
- The assistant's bubbles: their color, their text, an optional thin line around them, and the
links in them.
* - ``outgoingMessage``
- ``backgroundColor``, ``color``, ``borderColor``, ``linkColor``
- The user's bubbles, likewise.
* - ``messageInput``
- ``backgroundColor``, ``editorBackgroundColor``, ``color``, ``placeholderColor``, ``borderColor``
- The strip at the bottom (``backgroundColor``), the box itself (``editorBackgroundColor``), what the
user types (``color``), the hint in the empty box (``placeholderColor``, whose words are
``spec.config.prompt.appPlaceholder``), and the line above the strip.
* - ``buttons``
- ``color``
- The send and attach buttons, and the header's buttons.
* - ``typingIndicator``
- ``color``
- "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:
.. code-block:: yaml
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.
.. code-block:: yaml
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.
.. code-block:: yaml
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.
.. code-block:: yaml
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.
.. code-block:: yaml
messageInput:
editorBackgroundColor: "#f1f1f1"
placeholderColor: "#6c757d"
buttons:
color: "#fca311"
typingIndicator:
color: "#fca311"
Step 6: check, and ship
~~~~~~~~~~~~~~~~~~~~~~~
Before deploying, the prompt engineer checks three things:
1. **Can everyone read it?** Each text color is checked against its background with WebAIM's free
`contrast checker `__. Aim for its "WCAG AA: Pass".
2. **Does it work on a phone?** The workbench is resized to a phone's width.
3. **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:
.. literalinclude:: ../../../../smarter/smarter/apps/plugin/data/stackademy/stackademy-llmclient-sql.yaml
:language: yaml
:caption: stackademy-llmclient-sql.yaml
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
:doc:`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:
.. code-block:: css
.SmarterChat .cs-conversation-header {
background-color: var(--smarter-chat-header-bg, #f6fbff);
}
The properties are named ``--smarter-chat--``, 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.