Theming and layout
The chat panel has two layouts and one styling mechanism. This page explains both, with the exact rules the panel follows, so you can fit it into your page without trial and error.
Two layouts
| Docked in a container | Floating window | |
|---|---|---|
| How you get it | contained: true (or the contained attribute) | The default |
| Where the panel is | Inside your mount target, 10 px from its top-left corner | Appended to document.body, fixed 10 px from the top-left corner of the browser viewport |
| Moves with the page when it scrolls | Yes | No |
| Place cards and minimized chats | Stay inside the mount target | Positioned against the viewport |
| Stacking | Inside your container's stacking order | z-index 10000 |
| Use it when | The map is one region of a page: a column, a card, a section | The map fills the whole viewport |
Most pages want the docked layout. The floating window does not know where your map is: on a page where the map is halfway down, the panel still sits at the top-left of the window.
Docking the panel in a container
contained: true puts the panel inside the element you mount into and positions it against that element. For
that to work, the element must be positioned and must have a size. The reliable way to give it both is
one wrapper that holds two layers: the map, and the chat mount target on top of it.
<div id="mapHost" style="position:relative; height:520px">
<div id="map" style="position:absolute; inset:0; z-index:0"></div>
<div id="chat" style="position:absolute; inset:0; z-index:1; pointer-events:none"></div>
</div>
Kaleidr.mount('#chat', {
product: 'chat',
publishableKey: 'kld_pk_live_…',
map,
contained: true,
});
Each declaration in that markup has a job:
| Declaration | Why it is there |
|---|---|
position:relative and a height on #mapHost | Gives both layers a box to fill. The height can come from anywhere (a fixed value, a flex or grid track, 100vh), but it must resolve to a real size. |
position:absolute; inset:0 on #map | The map fills the wrapper. |
z-index:0 on #map | Creates a stacking context for the map, so the map library's own z-index values (Leaflet uses values up to 1000) stay inside the map layer and cannot rise above the chat layer. |
position:absolute; inset:0 on #chat | Makes the mount target positioned and gives it the map's size. The panel is placed 10 px from this box's top-left corner and is never taller than this box minus 30 px. |
z-index:1 on #chat | Puts the chat layer above the map layer. |
pointer-events:none on #chat | Lets clicks, drags and scroll reach the map everywhere the panel is not. The panel, its place cards and its minimized chats switch pointer events back on for themselves. |
What goes wrong without them:
| Symptom | Cause |
|---|---|
| The panel appears at the top-left of the page or of some outer element, not over the map | The mount target is not positioned, so the panel is placed against the nearest positioned ancestor |
| The panel appears at the top-left of the browser window and stays there when the page scrolls | contained is missing |
| The panel is cut off or has no room for answers | The mount target has no height |
| The map cannot be dragged | The chat layer covers the map and pointer-events:none is missing |
| The panel is hidden behind the map or its controls | The map layer has no stacking context of its own; add z-index:0 to it |
With the element
The same layout with <kaleidr-map> instead of Kaleidr.mount. The element is the mount target, so the
positioning styles go on the element itself. <kaleidr-map> is display:block by default; the snippets write
display:block out so that the layout also holds in Safari before 16.4, where the element stays inline.
<div style="position:relative">
<kaleidr-map id="store-map" product="viewer" share-id="YOUR_MAP_ID"
style="display:block; height:480px"></kaleidr-map>
<kaleidr-map product="chat" share-id="YOUR_MAP_ID" contained
map-target="#store-map"
style="display:block; position:absolute; inset:0; z-index:1; pointer-events:none"></kaleidr-map>
</div>
The element cannot receive a live map object, so this form is for driving a Kaleidr viewer or tile element, or
for a chat-only panel. To attach to your own Mapbox, MapLibre, Google or Leaflet map, use Kaleidr.mount.
The floating window
Without contained, the panel is appended to document.body and uses position:fixed. It starts as an input
bar 10 px from the top and left edges of the viewport, and expands downward into a chat window when the visitor
sends a question. The mount target you pass still has to exist, but it does not determine where the panel is.
Three things about this layout matter in practice:
-
It ignores your map's position. Use it when the map is the page.
-
It sits above your content. The panel and its place cards use
z-index10000. Minimized chats sit above that. A site header, drawer or modal with a lowerz-indexappears underneath the panel. -
It can be shifted right, but not down. The property
--app-left-insetmoves the floating window to the right by the length you give it, and narrows its maximum width by the same amount. Use it to clear a fixed sidebar:Kaleidr.mount('#chat', {product: 'chat',publishableKey: 'kld_pk_live_…',map,theme: { '--app-left-inset': '280px' },});There is no property that moves the floating window down or to another corner. The 10 px top and left margins are fixed on the panel itself, so setting margin properties through
themedoes not move it. If you need the panel somewhere else, usecontainedand position the container.
Stacking against your own layers
Set --kct-z-base to move the panel and its place cards to a different level. For example, to keep the panel
below a site header that uses z-index: 500:
Kaleidr.mount('#chat', {
product: 'chat',
publishableKey: 'kld_pk_live_…',
map,
theme: { '--kct-z-base': '400' },
});
Set it at mount. Minimized chats are placed 90,000 above the base and read it once, when the panel mounts; they sit at the bottom of the viewport, away from a typical header.
In the docked layout the panel is confined to your container's stacking context, so the z-index of the chat
layer decides how it stacks against the rest of the page, and --kct-z-base only orders things inside it.
Small screens
On a viewport 768 px wide or narrower, the panel switches to a bottom-sheet layout. In the docked layout the same switch happens when the container is 768 px wide or narrower and the device is a phone-class device (a narrow window or a touch-first pointer). A narrow map column on a desktop keeps the regular layout.
Theme tokens
The panel is styled with CSS custom properties. These are the ones a page usually changes. Each default is the value the panel uses when you set nothing.
| Property | Default | What it controls |
|---|---|---|
--highlight-color | #2da84a | The accent color, used across the panel, place cards and pins. |
--highlight-color-on | #ffffff | Text and icons drawn on top of the accent. |
--glass-bg | rgba(255, 255, 255, 1) | The background of the panel, the chat window and place cards. |
--ui-bg-solid | #ffffff | The background of solid surfaces such as dialogs. |
--glass-border | 1px solid rgba(0, 0, 0, 0.08) | The outline of the panel and cards. |
--color-primary-white | rgba(0, 0, 0, 0.88) | Primary text. The name is historical; the default is dark. |
--color-secondary-white | rgba(0, 0, 0, 0.55) | Secondary text. |
--ui-font-scale | 1.2 | A multiplier on every text size and on spacing. The intended range is 0.8 to 1.6. |
--kct-z-base | 10000 | The z-index of the panel and its place cards. Set it at mount. |
--app-left-inset | 0px | A horizontal offset for the floating window. No effect in the docked layout. |
There is no --kd-accent property. The accent is --highlight-color.
The panel has no built-in dark theme and does not follow prefers-color-scheme. For a dark panel, set the
surface and text properties yourself:
Kaleidr.mount('#chat', {
product: 'chat',
publishableKey: 'kld_pk_live_…',
map,
contained: true,
theme: {
'--highlight-color': '#8b5cf6',
'--glass-bg': 'rgba(24, 24, 27, 0.96)',
'--ui-bg-solid': '#18181b',
'--glass-border': '1px solid rgba(255, 255, 255, 0.12)',
'--color-primary-white': 'rgba(255, 255, 255, 0.92)',
'--color-secondary-white': 'rgba(255, 255, 255, 0.6)',
},
});
Check the result in your page. The table lists the main properties, not every one the stylesheet defines.
Setting the theme
There are three ways in, and they do the same thing.
At mount, as an object:
Kaleidr.mount('#chat', {
product: 'chat',
publishableKey: 'kld_pk_live_…',
map,
contained: true,
theme: { '--highlight-color': '#7c3aed', '--ui-font-scale': '1' },
});
On the element, as a JSON string:
<kaleidr-map product="chat" publishable-key="kld_pk_live_…" contained
theme='{"--highlight-color":"#7c3aed"}'
style="display:block; position:absolute; inset:0; z-index:1; pointer-events:none"></kaleidr-map>
Changing the theme attribute after the element has mounted applies the new value.
After mount, with setTheme:
const handle = Kaleidr.mount('#chat', { product: 'chat', publishableKey: 'kld_pk_live_…', map });
handle.setTheme({ '--highlight-color': '#0ea5e9' });
handle.setTheme('{"--ui-font-scale":"1.4"}'); // a JSON string works too
Rules that apply to all three:
- The value is an object of property names to string values, or a JSON string of such an object.
- A key without the leading
--gets one added:{ 'highlight-color': '#7c3aed' }works. setThemeadds to what is already set. It does not reset properties you leave out.- Named themes such as
"dark"do not exist. A string that is not JSON, or an array, is ignored with a[kaleidr]warning in the console. setThemecan be called immediately aftermount. The call is queued until the panel has loaded.
Themes are page-wide
The properties are written to the <html> element's inline style (document.documentElement.style). They are
not scoped to the panel or to the mount. Three consequences:
-
Two chat mounts on one page share one theme. The last value written wins for both.
-
Your own CSS sees the properties. If your stylesheet reads
var(--highlight-color), it now gets the value you gave the panel. -
destroy()does not remove them. Clear a property yourself when you need to:document.documentElement.style.removeProperty('--highlight-color');
Property names you may already use
The panel's stylesheet gives every property it uses a default on :root, at zero specificity. Your own
stylesheet always wins over those defaults. That cuts both ways:
- If your site already defines
--highlight-color,--glass-bg,--ui-font-scaleor another property from the table on:root, the panel uses your value without being asked. If that is not what you want, pass the panel's own value throughtheme, which is an inline style and overrides both. - If your CSS reads one of the panel's property names without defining it (for example
var(--spacing-base, 8px)), it resolves to the panel's default once the panel has mounted, not to your fallback.
The panel's sizes, spacing, radii and margins are also set directly on the panel's own elements, so a value your site defines for those names does not change the panel.
What you cannot change
- The language of the panel's own labels, such as its buttons. The header title, the input placeholder and the
language of the answers are mount options, not theme tokens:
title,placeholderandlang. See Title and placeholder. - The corner the panel starts in, or its 10 px margins.
- The panel's font. It ships its own typeface, embedded in the stylesheet.
- The look of the pins beyond the accent color.