Style the widget
The widget ships structure only, no visual CSS — on purpose, so it inherits nothing it shouldn’t and clashes with nothing on your page. You bring the looks. Paste the starter stylesheet below and you get a tidy, branded chat box to build from.
Quick start
Everything is driven by the five variables at the top of the snippet: change the colours and the font and you’re most of the way there. It comes in copadre’s own colours, light by default. For a dark version, add class="copadre-dark" to your container <div>.
Paste this into your page’s <head>:
<style>
/* ============================================================
copadre embed widget — starter styles
Paste into your own page's <head>, then change the variables
below to match your site. Light by default; add
class="copadre-dark" to the container div for the dark version.
============================================================ */
#copadre-container {
/* ---- Edit these five to rebrand the whole widget ---- */
--copadre-accent: #f9e231; /* buttons & focus */
--copadre-on-accent:#1a1a18; /* text on the button */
--copadre-bg: #faf8f1; /* widget background */
--copadre-text: #1a1a18; /* message text */
--copadre-font: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
/* ---- Derived; usually leave alone ---- */
--copadre-muted: #5b5a54;
--copadre-border: #e3ddcc;
--copadre-radius: 14px;
--copadre-dot-size: 7px; /* "thinking" dot diameter */
--copadre-log-height: 320px; /* transcript height; for a widget that grows
with content instead, swap the height/overflow
on .copadre-output below for max-height */
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 560px;
padding: 1.25rem;
font-family: var(--copadre-font);
color: var(--copadre-text);
background: var(--copadre-bg);
border: 1px solid var(--copadre-border);
border-radius: var(--copadre-radius);
box-sizing: border-box;
}
/* Dark variant — add class="copadre-dark" to the container */
#copadre-container.copadre-dark {
--copadre-bg: #1a1a18;
--copadre-text: #eceae1;
--copadre-muted: #a4a299;
--copadre-border: #3a3a35;
}
/* Transcript — the scrolling message log. For a widget that grows with its
content instead of a fixed box, replace height + overflow-y with
max-height: var(--copadre-log-height); */
.copadre-output {
display: flex;
flex-direction: column;
gap: 0.6rem;
height: var(--copadre-log-height);
overflow-y: auto;
}
/* Message bubbles */
.copadre-msg {
max-width: 85%;
padding: 0.55rem 0.8rem;
line-height: 1.5;
border-radius: 12px;
overflow-wrap: anywhere;
}
.copadre-msg--user {
align-self: flex-end;
color: var(--copadre-on-accent);
background: var(--copadre-accent);
border-bottom-right-radius: 3px;
}
.copadre-msg--bot {
align-self: flex-start;
background: rgba(127, 127, 127, 0.14);
border-bottom-left-radius: 3px;
}
/* Markdown inside a reply */
.copadre-msg p { margin: 0 0 0.75em; }
.copadre-msg p:last-child { margin-bottom: 0; }
.copadre-msg a { color: inherit; }
.copadre-msg code {
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-size: 0.9em;
padding: 0.1em 0.35em;
border-radius: 5px;
background: rgba(127, 127, 127, 0.18);
}
.copadre-msg ul { margin: 0 0 0.75em; padding-left: 1.25em; }
.copadre-msg li { margin: 0.2em 0; }
/* Replies arrive a word at a time; this fades each one in. Optional —
without it the words simply appear. */
@media (prefers-reduced-motion: no-preference) {
.copadre-word { transition: opacity 220ms ease-out; }
}
/* "Thinking" indicator — three jumping dots while the reply loads */
.copadre-dot {
display: inline-block;
width: var(--copadre-dot-size);
height: var(--copadre-dot-size);
margin-right: 0.3rem;
border-radius: 50%;
background: var(--copadre-muted);
animation: copadre-bounce 1.2s infinite ease-in-out both;
}
.copadre-dot:last-child { margin-right: 0; }
.copadre-dot:nth-child(2) { animation-delay: 0.18s; }
.copadre-dot:nth-child(3) { animation-delay: 0.36s; }
@keyframes copadre-bounce {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-4px); opacity: 1; }
}
/* Input row */
.copadre-form {
display: flex;
align-items: center;
gap: 0.5rem;
}
.copadre-input {
flex: 1 1 auto;
min-width: 0;
padding: 0.7rem 0.9rem;
font: inherit;
color: var(--copadre-text);
background: transparent;
border: 1px solid var(--copadre-border);
border-radius: 9px;
}
.copadre-input::placeholder { color: var(--copadre-muted); }
.copadre-input:focus {
outline: 2px solid var(--copadre-accent);
outline-offset: 1px;
border-color: var(--copadre-accent);
}
/* Send button — round, with a play-triangle. Disables while awaiting a reply. */
.copadre-submit {
position: relative;
flex: 0 0 auto;
width: 40px;
height: 40px;
cursor: pointer;
background: var(--copadre-accent);
border: none;
border-radius: 50%;
}
.copadre-submit::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
transform: translate(-60%, -50%);
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-left: 10px solid var(--copadre-on-accent);
}
.copadre-submit:hover { filter: brightness(0.95); }
.copadre-submit:disabled { opacity: 0.6; cursor: default; }
</style>
A starting point, not a cage — every class above is part of the widget’s stable styling contract, so anything you can do in CSS, you can do here.
Class definitions
The widget renders the same set of elements every time, with these stable class names. Style any of them; they won’t change underneath you.
Markdown inside a reply — links, paragraphs, lists, inline code — carries no classes of its own. Style it by element within a bubble: .copadre-msg a for links (always relative to your own site, opened in a new tab), and .copadre-msg p, .copadre-msg ul, .copadre-msg code for the rest, as the starter stylesheet above does.
| Selector | What it is |
|---|---|
#copadre-container | The container you place (or that the widget creates). The styling root — put your variables here. |
.copadre-output | The scrolling transcript log. |
.copadre-msg | One message bubble. |
.copadre-msg--user | A visitor message bubble. |
.copadre-msg--bot | A bot message bubble (replies, errors, the thinking dots). |
.copadre-word | One word of a reply, as it is revealed. Style the fade here — words are fully visible without any rule. |
.copadre-thinking | Modifier on the bot bubble while it shows the loading dots. |
.copadre-dot | One of the three dots inside a thinking bubble. |
.copadre-form | The input + send-button wrapper. |
.copadre-input | The text input. |
.copadre-submit | The icon-only send button (disabled while awaiting a reply). |
Already animated. While the widget waits for a reply it shows the familiar three jumping dots — the snippet above styles and animates them, so you can recolour, resize or restyle them however you like via --copadre-dot-size and the .copadre-dot rules.
Updating from an earlier snippet? Replies are revealed a word at a time, and the .copadre-word rule fades each one in. It’s optional — if your stylesheet predates it the words simply appear instead, and nothing else changes. Both respect a visitor’s reduced-motion setting.