Skip to main content

CSS Reference

Compatibility
This reference compatible with Better Messages 3.0.0 or higher

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:

  1. 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
  2. Design tokens — the CSS custom properties the Appearance tab writes. Set one yourself and every surface that reads it follows
  3. Class names — for the things no token covers
  4. 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_customizations filter, 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;
} );
Never edit the plugin's stylesheet

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.

TokenWhat it paints
--bm-color-bgThe main background
--bm-color-bg-secondaryFields, and the tone one step off the background
--bm-color-bg-elevatedPanels and popovers raised off it
--bm-color-bg-hover, --bm-color-bg-activeRow and button states
--bm-color-text-primaryBody text
--bm-color-text-secondary, --bm-color-text-tertiarySupporting and dimmed text
--bm-color-preview, --bm-color-preview-unreadThe conversation list's message preview line
--bm-color-iconIcon strokes
--bm-color-border, --bm-color-border-subtle, --bm-color-border-strongThe three border weights
--bm-color-accent, --bm-color-accent-hover, --bm-color-accent-pressed, --bm-color-accent-subtleThe accent and its states
--bm-color-text-on-accentText drawn on the accent
--bm-color-bubble-self-bg / -text / -nicknameOwn messages
--bm-color-bubble-other-bg / -text / -nicknameEveryone else's messages
--bm-color-sticky-date-bg / -textThe floating day pill
--bm-color-mention-bg / -textA mention of the reader
--bm-color-tooltip-bg / -textTooltips
--bm-color-online, --bm-color-away, --bm-color-busy, --bm-color-offline, --bm-color-recordingPresence 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:

ZonePrefixSurface
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.

Write against the shared name, not the zone name

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#

FamilyTokens
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:

ClassWhere it comes from
.bm-wrap-mainThe main messenger page
.bm-chat-wrapA chat room, also carrying data-chat-id, data-thread-id and data-full-screen
.bm-single-thread-wrapOne embedded conversation, carrying data-thread-id
.bm-wrap-groupA community group's conversation
.bm-threads-listThe standalone conversations-list widget
.bm-mini-widgets-wrapThe corner widget bar
.bm-mini-chats-wrapThe mini chat dock
.bm-mini-chatOne mini chat window, carrying data-chat-id
.bm-scopeAnything 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#

ClassWhat it is
.bm-cardThe card the messenger is drawn on — corners, shadow, clipping
.bm-inboxThe conversations column — .bm-inbox__rows is its scroller
.bm-inbox-railThe collapsed icon rail
.bm-embedded-inboxThe inbox aside of the embedded messenger
.bm-thread-rowOne conversation in the list, with data-active and data-unread
.bm-thread-row-title, .bm-thread-row-preview, .bm-thread-row-chipIts name line, preview line and badges
.bm-thread-colThe conversation column
.bm-thread-headerIts header — .bm-thread-header-action for the buttons in it
.bm-msg-listThe scrolling message list, laid out column-reverse
.bm-msg-stackA run of consecutive messages from one sender
.bm-msg-main, .bm-msg-meta, .bm-msg-statusA bubble's body, its time line, its delivery tick
.bm-day-sep, .bm-day-pillDay separators
.bm-unread-dividerThe "new messages" line
.bm-composerThe reply area — .bm-format-toolbar floats above it
.bm-reactions, .bm-reaction-pill, .bm-reaction-stripReactions
.bm-typing-dotsThe typing animation
.bm-presence-dotA presence dot — .bm-presence-label is its text
.bm-avatar-wrap, .bm-avatar-img, .bm-avatar-stackAvatars, single and grouped
.bm-outbox-bannerThe card on a message the server refused
.bm-context-menu, .bm-context-panelThe message menu and the conversation info panel
.bm-emoji-popoverThe emoji / sticker / GIF picker, portalled out of the card
.bm-call-surfaceThe 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.jsbm-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 mod3.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.

The old Custom CSS field was removed, and its contents are not migrated

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 :root but overridden inside a zone
  • specificity, because several of the plugin's own rules are written against body to 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#