CSS Reference
Better Messages draws its interface with React, so there are no PHP templates to override. Everything visual comes from four layers, and they are worth trying in this order:
- The Appearance tab — colors, corners, spacing, typography, dark mode and per-surface overrides, all with a live preview. Most of what people used to write CSS for is a control now. See Easy customization
- Design tokens — the CSS custom properties the Appearance tab writes. Set one yourself and every surface that reads it follows
- Class names — for the things no token covers
- JavaScript filters — for adding your own classes, attributes and markup per thread, message or user. See the JS filters reference
Only drop a layer when the one above genuinely cannot express what you want.
Where to put your CSS#
Anywhere that loads on the front end and survives a plugin update:
- Appearance → Customize → Additional CSS — simplest, stored per theme
- A child theme stylesheet
- A code-snippets plugin
- The
better_messages_css_customizationsfilter, which takes an array of rule strings and prints them with the messenger's own styles:
add_filter( 'better_messages_css_customizations', function( $rules ) {
$rules[] = '.bm-thread-row { --bm-density: 1.2; }';
return $rules;
} );
assets/css/better-messages.css is compiled from SCSS and replaced on every update. The same goes
for better-messages.min.css, location.css and mobile-app.css.
Layer 2 — design tokens#
Every token is a CSS custom property named --bm-*, declared on :root by a <style> block the
plugin prints on each page. Redeclare one in your own stylesheet — or scope it to a single surface,
which is how you restyle one chat room without touching the rest.
Colors are RGB triplets, not hex#
Most color tokens hold a comma-separated RGB triplet, because the stylesheet consumes them as
rgba(var(--bm-color-accent), 1) and varies the alpha from a single stored value. A hex value
produces invalid CSS and the rule silently does nothing.
/* correct */
:root { --bm-color-accent: 220, 38, 38; }
/* broken — nothing happens */
:root { --bm-color-accent: #dc2626; }
The shared color names#
These are the names the whole interface is drawn from. Everything else is derived from them.
| Token | What it paints |
|---|---|
--bm-color-bg | The main background |
--bm-color-bg-secondary | Fields, and the tone one step off the background |
--bm-color-bg-elevated | Panels and popovers raised off it |
--bm-color-bg-hover, --bm-color-bg-active | Row and button states |
--bm-color-text-primary | Body text |
--bm-color-text-secondary, --bm-color-text-tertiary | Supporting and dimmed text |
--bm-color-preview, --bm-color-preview-unread | The conversation list's message preview line |
--bm-color-icon | Icon strokes |
--bm-color-border, --bm-color-border-subtle, --bm-color-border-strong | The three border weights |
--bm-color-accent, --bm-color-accent-hover, --bm-color-accent-pressed, --bm-color-accent-subtle | The accent and its states |
--bm-color-text-on-accent | Text drawn on the accent |
--bm-color-bubble-self-bg / -text / -nickname | Own messages |
--bm-color-bubble-other-bg / -text / -nickname | Everyone else's messages |
--bm-color-sticky-date-bg / -text | The floating day pill |
--bm-color-mention-bg / -text | A mention of the reader |
--bm-color-tooltip-bg / -text | Tooltips |
--bm-color-online, --bm-color-away, --bm-color-busy, --bm-color-offline, --bm-color-recording | Presence dots |
--bm-color-success, --bm-color-warning, --bm-color-danger, --bm-color-info (each with a -bg) | Status colors |
Zones: why --bm-color-bg means something different in each surface#
The Appearance tab lets a site color the conversation header, the reply area, the conversations list, the mini chat, the widget bar and the mobile surfaces independently. It does that without a second set of class names: each surface re-points the shared token names to its own values.
There are 14 zones and each one has its own copy of the names it needs:
| Zone | Prefix | Surface |
|---|---|---|
header | --bm-color-header-* | Conversation header |
reply | --bm-color-reply-* | Reply area |
secondary | --bm-color-inbox-* | Side conversations list |
list | --bm-color-list-* | Conversations list |
mc-head | --bm-color-mc-head-* | Mini chat header |
mc | --bm-color-mc-* | Mini chat messages |
mc-reply | --bm-color-mc-reply-* | Mini chat reply area |
dock | --bm-color-dock-* | Widget bar |
wlist | --bm-color-wlist-* | Widget list |
mob-head | --bm-color-mob-head-* | Mobile header |
mob | --bm-color-mob-* | Mobile messages |
mob-reply | --bm-color-mob-reply-* | Mobile reply area |
mob-list | --bm-color-mob-list-* | Mobile list |
mob-tabs | --bm-color-mob-tabs-* | Mobile tab bar |
The part after the prefix is the same vocabulary everywhere — bg, text, muted, preview,
icon, border, field, accent, accent-text, self-bg, self-text, self-name,
other-bg, other-text, other-name — and each zone carries only the parts its surface has.
Inside a zone's DOM the shared names have already been re-pointed, so a rule that reads
rgba(var(--bm-color-bg), 1) in the header gets the header's background, and the same rule in the
mini chat gets the mini chat's. You almost never need to name a zone token.
The redirection runs through a third family, --bm-zone-<zone>-<name>, which is what the plugin
actually prints. Searching a stylesheet for var(--bm-color-header-bg) therefore turns up nothing
and does not mean the control is dead.
Size, shape and typography#
| Family | Tokens |
|---|---|
| Radii | --bm-radius-xs/sm/md/lg, --bm-radius-card, --bm-radius-panel, --bm-radius-popover, --bm-radius-tooltip, --bm-radius-input, --bm-radius-button, --bm-radius-pill, --bm-radius-circle, --bm-radius-bubble, --bm-radius-bubble-head, --bm-radius-date-pill, --bm-radius-avatar |
| Spacing | --bm-space-2xs/xs/sm/md/lg/xl/2xl, and --bm-density, a multiplier most paddings are written against |
| Type | --bm-messenger-font, --bm-font-family-mono, --bm-font-size-xs … --bm-font-size-2xl, --bm-font-weight-regular/medium/semibold/bold, --bm-line-height-tight/normal/relaxed |
| Messenger box | --bm-min-height, --bm-max-height, --bm-viewport-offset, --bm-sidebar-width, --bm-bubble-max-width |
| Mini widgets | --bm-mini-widget-width/height, --bm-mini-widget-offset-x/-y, --bm-mini-chat-width/height, --bm-mini-chats-offset, --bm-bubble-size, --bm-radius-mini-widget, --bm-radius-mini-chat-button, --bm-radius-bubble-button, --bm-mobile-button-bottom |
| Motion | --bm-duration-instant/fast/base/slow/slower, --bm-ease-out, --bm-ease-in-out |
| Depth | --bm-shadow-xs/sm/md, --bm-shadow-card, --bm-shadow-input, --bm-shadow-popover, --bm-shadow-modal, --bm-z-overlay, --bm-z-tooltip |
--bm-density is the one to reach for first when the interface feels too tight or too loose: it
scales the paddings across rows, bubbles and the composer at once.
Dark mode#
The dark values are printed under two selectors, and an explicit light class always wins:
@media (prefers-color-scheme: dark) {
body:not(.bm-messages-light) { /* dark tokens */ }
}
body.bm-messages-dark { /* dark tokens */ }
Override a token for dark mode by matching the same shape:
body.bm-messages-dark,
body:not(.bm-messages-light) { --bm-color-accent: 129, 199, 132; }
Layer 3 — class names#
Every class is prefixed bm-. Blocks are named plainly and their pieces use __
(.bm-inbox → .bm-inbox__rows). Variant state is carried on data-* attributes rather than
modifier classes wherever the state is something React knows, which is what makes a row fully
re-styleable without touching the component.
Root wrappers#
One of these is the outermost element of any Better Messages surface, and they are the right place to scope a rule:
| Class | Where it comes from |
|---|---|
.bm-wrap-main | The main messenger page |
.bm-chat-wrap | A chat room, also carrying data-chat-id, data-thread-id and data-full-screen |
.bm-single-thread-wrap | One embedded conversation, carrying data-thread-id |
.bm-wrap-group | A community group's conversation |
.bm-threads-list | The standalone conversations-list widget |
.bm-mini-widgets-wrap | The corner widget bar |
.bm-mini-chats-wrap | The mini chat dock |
.bm-mini-chat | One mini chat window, carrying data-chat-id |
.bm-scope | Anything the plugin renders outside the wrappers above |
.bm-mobile is added to <html> and <body> on the full-screen mobile layout — it is the marker
to scope mobile-only rules against.
Inside the messenger#
| Class | What it is |
|---|---|
.bm-card | The card the messenger is drawn on — corners, shadow, clipping |
.bm-inbox | The conversations column — .bm-inbox__rows is its scroller |
.bm-inbox-rail | The collapsed icon rail |
.bm-embedded-inbox | The inbox aside of the embedded messenger |
.bm-thread-row | One conversation in the list, with data-active and data-unread |
.bm-thread-row-title, .bm-thread-row-preview, .bm-thread-row-chip | Its name line, preview line and badges |
.bm-thread-col | The conversation column |
.bm-thread-header | Its header — .bm-thread-header-action for the buttons in it |
.bm-msg-list | The scrolling message list, laid out column-reverse |
.bm-msg-stack | A run of consecutive messages from one sender |
.bm-msg-main, .bm-msg-meta, .bm-msg-status | A bubble's body, its time line, its delivery tick |
.bm-day-sep, .bm-day-pill | Day separators |
.bm-unread-divider | The "new messages" line |
.bm-composer | The reply area — .bm-format-toolbar floats above it |
.bm-reactions, .bm-reaction-pill, .bm-reaction-strip | Reactions |
.bm-typing-dots | The typing animation |
.bm-presence-dot | A presence dot — .bm-presence-label is its text |
.bm-avatar-wrap, .bm-avatar-img, .bm-avatar-stack | Avatars, single and grouped |
.bm-outbox-banner | The card on a message the server refused |
.bm-context-menu, .bm-context-panel | The message menu and the conversation info panel |
.bm-emoji-popover | The emoji / sticker / GIF picker, portalled out of the card |
.bm-call-surface | The call stage beside the conversation |
This is the structural set, not an exhaustive list — the stylesheet carries around 214 top-level classes. Anything not listed here is internal: inspect it in DevTools by all means, but expect it to move between releases.
Scoping to one surface#
/* Only the chat room with id 123 */
.bm-chat-wrap[data-chat-id="123"] {
--bm-color-accent: 16, 185, 129;
--bm-radius-bubble: 4px;
}
/* Only unread rows in the conversations list */
.bm-thread-row[data-unread="1"] .bm-thread-row-title { font-weight: 700; }
/* Only the full-screen mobile layout */
body.bm-mobile .bm-composer { padding-bottom: 12px; }
Coming from 2.x#
Three renames land at once, and all three break CSS written for 2.x.
Class prefixes. Every messenger class is now bm-, replacing bpbm- and bp-messages-. Do
not search-and-replace: the redesign changed structure as well as names, so check each selector
against the live DOM rather than assuming bp-messages-x became bm-x.
Asset filenames. bp-messages* → better-messages*, and bpbm-worker.js → bm-worker.js.
Update the exclusion lists of any caching or optimisation plugin, or it will keep excluding files
that no longer exist.
Customizer theme mods → tokens. The WordPress Customizer integration is gone, and
Better_Messages()->customize with it. Values are migrated automatically on upgrade. This is the
map, for anyone who has to translate a snippet by hand:
| 2.x theme mod | 3.0 token |
|---|---|
main-bm-color | --bm-color-accent |
bm-primary-bg | --bm-color-bg |
bm-secondary-bg | --bm-color-inbox-bg |
bm-hover-bg | --bm-color-bg-hover |
bm-primary-border | --bm-color-border |
bm-secondary-border | --bm-color-border-subtle |
bm-text-color | --bm-color-text-primary |
bm-modern-left-side-bg / -color / -nickname | --bm-color-bubble-other-bg / -text / -nickname |
bm-modern-right-side-bg / -color / -nickname | --bm-color-bubble-self-bg / -text / -nickname |
bm-sticky-date-bg / -color | --bm-color-sticky-date-bg / -text |
bm-tooltip-bg / -color | --bm-color-tooltip-bg / -text |
bm-border-radius | --bm-radius-button |
bm-modern-radius | --bm-radius-bubble |
bm-date-radius | --bm-radius-date-pill |
bm-widgets-border-radius | --bm-radius-mini-widget |
bm-widgets-button-radius | --bm-radius-mini-chat-button |
bm-bubble-radius | --bm-radius-bubble-button |
bm-bubble-size | --bm-bubble-size |
bm-mini-widgets-width / -height | --bm-mini-widget-width / -height |
bm-mini-widgets-indent / -bottom | --bm-mini-widget-offset-x / -y |
bm-mini-chats-width / -height | --bm-mini-chat-width / -height |
Each *-dark theme mod maps to the same token in the dark scheme.
The four size settings that used to live on the settings page became tokens too:
fixedHeaderHeight → --bm-viewport-offset, messagesMinHeight → --bm-min-height,
messagesHeight → --bm-max-height, mobilePopupLocationBottom → --bm-mobile-button-bottom.
If you are still on 2.x, copy what is in Design → Custom CSS out before you update.
Frequently asked questions#
Which token does a given control on the Appearance tab write?#
Open the browser inspector on the element you want, and read the custom properties in the computed
styles — the tab writes the tokens listed here and nothing else. Where a zone override is in play
you will see the shared name resolving through --bm-zone-<zone>-<name>.
My rule has no effect. Why?#
In order of likelihood:
- a color written as hex instead of an RGB triplet
- a token set on
:rootbut overridden inside a zone - specificity, because several of the plugin's own rules are written against
bodyto out-specify leftovers - the class no longer exists under that name, which is what happens to most CSS carried over from 2.x
Can I style a single chat room, or a single conversation?#
Yes — .bm-chat-wrap[data-chat-id="123"] for a room and [data-thread-id="456"] for a
conversation, on .bm-single-thread-wrap or on a row in the list.
Do my customizations survive an update?#
Tokens set in your theme or through the filter, yes. Class names are a contract only for the structural set above. The rest can change in any release.
Is there a Sass or SCSS entry point I can import?#
No. The plugin's SCSS is a build-time source, not a published API — write plain CSS against the tokens.
See also#
- Easy customization — the Appearance tab, which is where to start
- CSS snippets — worked examples
- JS filters — adding your own classes, attributes and markup
- White label — removing Better Messages branding