Use when writing CSS for Discourse core, themes, or plugins - handles BEM class naming, modifiers with double dashes, block__element structure, and is-/has- state prefixes
/plugin marketplace add discourse/skills/plugin install discourse-frontend-skills@discourse/skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Discourse uses a modified BEM (Block Element Modifier) variant. Double dashes for modifiers, double underscores for elements, special prefixes for states.
Utility classes are exempt from BEM naming. Simple, reusable utilities use single-word names:
.hidden, .show, .sr-only, .clickablecommon/foundation/helpers.scss!important to override component stylesUse BEM for: Component-specific styling, reusable blocks Skip BEM for: Generic utilities, helpers, foundation classes
| Pattern | Syntax | Example |
|---|---|---|
| Block | .block | .d-button, .chat-message |
| Element | .block__element | .chat-message__avatar, .user-form__input |
| Modifier | .--modifier | .--cancel, .--highlighted |
| State | .is-foo, .has-foo | .is-open, .has-errors |
Critical differences from standard BEM:
--modifier), not single dash (-modifier)is-, has-), not modifiers.d-button {
background: var(--primary);
&.--cancel {
background: var(--secondary);
}
&.--danger {
background: var(--danger);
}
}
HTML: <button class="d-button --cancel"></button>
.user-form {
&__input {
border: 1px solid var(--primary-low);
// Indirect modifier - applies when parent has --error
.--error & {
border-color: var(--danger);
}
}
&__label {
color: var(--primary);
.--error & {
color: var(--danger);
}
}
}
HTML: <div class="user-form --error">...</div>
Use indirect when: Styling multiple child elements based on parent state
States use is- or has- prefixes:
.modal {
display: none;
&.is-open {
display: block;
}
}
.form {
&.has-errors {
.form__input {
border-color: var(--danger);
}
}
}
Always nest related elements under block:
.chat-message {
// block styling
display: flex;
&__avatar {
// element styling
width: 40px;
height: 40px;
}
&__content {
flex: 1;
}
&__username {
font-weight: bold;
}
&.--highlighted {
// direct modifier
background: var(--highlight-bg);
}
}
| Mistake | Correct |
|---|---|
.block .element | .block__element |
.block-element | .block__element |
.-modifier (dash) | .--modifier (double dash) |
.block.error | .block.has-errors or .block.is-error |
.modifier.block | .block.--modifier |
Don't match incorrect patterns for "consistency." If existing code uses wrong syntax:
// ❌ WRONG - existing legacy code
.d-button.-cancel {
}
.d-button.-primary {
}
// ✅ CORRECT - your addition
.d-button.--success {
}
Best practice: Fix legacy patterns when touching the file (low-risk CSS change)
Minimum: Add new code correctly; don't propagate technical debt
Before committing CSS:
--modifier)block__element)is- or has- for statesFrom Discourse chat plugin loading skeleton:
.chat-skeleton {
&__body {
display: flex;
&.--with-avatar {
padding-left: 50px;
}
}
&__message {
&.is-loading {
animation: pulse 1.5s infinite;
}
}
}