Theme a forum

Embed
Preview of the card
<a href="https://forumulate.com/guides/forum-theme"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/1dca18c9-8977-4dfa-91df-774f0d81f5f2/social-card.svg?v=3e7ae33eec8b" alt="Theme a forum" width="600" height="315"></a>

theme.forum is the part of a space's theme that only a Community (forum) space reads: how its topic list, front page, categories, tags and sidebar, topic page, and reader account surfaces look, beyond the keys every space shares. It lives inside the same theme you already set with PUT or PATCH /orgs/{orgId}/spaces/{spaceId}/theme.

  • Setting theme.forum on a space of any other type is refused with a 422. A site theme may carry it; it then reaches the site's forums.

  • What a reader renders is forumThemeOf(theme): the forum defaults, under the site's theme, under the space's own. Every key you set wins.

  • Unset on a forum means the forum default — the clean look since 2026-10-12 (the defaults). look: "paper" brings back the previous forum look in one key.

  • GET …/theme always answers with what you stored, never the resolved theme, so you can send it back whole without freezing the defaults into it.

Topic list and dates

theme.forum.list shapes the topic list — the forum's own list and a front page's forum feed drawn as rows. Every key is optional; unset draws the list as it always was.

Key

Values

What it does

avatars

none, op, posters

Faces on rows: the opener's before the title, or the thread's posters (the opener, the latest replier, the most frequent — up to five; on the dense table a "Posters" column)

avatarSize

16–48 (px)

The faces' size. Unset: 24 (posters), 28 (opener)

columns

up to five of posters, replies, views, likes, activity

The dense table's stat columns, in order. Unset: replies, views, activity. Counts stay under the front door's showCounts

heat

{views, replies, likes}, each three thresholds [low, mid, high]

A number at or past a threshold is tinted, stronger at each step. A theme key, not a setting, because the theme already reaches the reader

excerpt

none, pinned, all

The excerpt under the title

excerptLines

1–4

How many lines it may take. Unset: 2

thumbnails

none, start, end

The topic's picture beside the row: its cover, else the first image in the opening post

thumbSize

40–160 (px)

Unset: 64

stateGlyphs

boolean

A lock on a closed thread, a box on an archived one, a bookmark on one you saved. Unset: on

tags

text, chips

chips draws each tag in its own colours (and tagStyle's look); unset is a plain text run on the dense table

density

compact, cozy, comfortable

Row padding. cozy is today's

paging

pages, more, infinite

more adds a "Show more" button under the list; infinite presses it as it scrolls into view. Both stay links to the next page

stats

{position: start|end, fields: [votes, answers, views], acceptedStyle: box|filled, style: text|pill, phone: strip|hide}

The Q&A stats box. Naming fields (or show) draws it and it replaces the vote column; a sub-key alone (phone, width, position, style) only styles stats a look or show turned on. style: "pill" draws the votes as a bordered "↑ 7" of fixed width; phone: "strip" puts the stats on one line above the title below 640px ("-1 votes 0 answers 4 views") and "hide" drops them there. Unset: words, and today's phone row

newTopicsBar

boolean

A "3 new topics — show" bar when topics are posted while the page is open (checked every minute while the tab is visible)

pin

word, glyph

A pinned row says "Pinned", or shows a pin

unpinForMe

boolean

An "Unpin for me" link under pinned rows, for signed-in readers

header

{case: upper|title|none, align: start|center, show: boolean}

The dense table's header row. show: false draws no header row at all. Unset: upper case, at the start, shown

titleSize, metaSize

a length

The row title's and meta line's type size

titleColor, titleColorDark

hex

The row title's ink, light and dark

statusGlyph

check, checkbox, none

checkbox: an empty box before an open question, a filled check once solved. none: no glyph (say the state in meta or the stats box instead). Unset: a check on solved ones only

titleLines

1–4, or {sm, md, lg}

How many lines a title wraps to before it is clamped — per breakpoint with an object (sm under 640px, md to 1024px, lg above; a missing step takes the narrower one's). Both anatomies. Unset: 1, today's single line with an ellipsis

kindChip

boolean

false: no "Question" / "Solved" chip before a question's title. Unset: drawn

meta

up to five of author, asked, category, state, reputation, in order

A muted line under the title: "kaito asked 9 hours ago in Q&A · Unanswered". author, asked and category read as one phrase; state (Unanswered / Solved / Closed / Archived) and reputation are their own segments. A row whose meta line names the author drops it from the line under the excerpt. Unset: none

byline

none, end

end: the opener's face, name, reputation and "asked 4 mins ago" at the row's end (Stack Overflow). Unset: none

categoryMark

badge, tile

tile: a 32px tile in the category's colour with its emoji or icon before the title. Unset: the badge only

tagsPosition

meta, after-excerpt

Where the tags sit: on the meta line, or under the excerpt. Unset: under the excerpt on the card list, on the badge line in the dense table

avatarShape

circle, square

The faces' shape. Unset: circle

posterOverlap

boolean

false: the poster faces sit side by side instead of overlapping. Unset: overlapping

{
  "forum": {
    "list": {
      "titleLines": { "sm": 3, "md": 2, "lg": 2 },
      "kindChip": false,
      "meta": ["author", "asked", "category", "state"],
      "stats": { "position": "start", "fields": ["votes", "answers", "views"], "style": "pill", "phone": "strip" },
      "byline": "end",
      "categoryMark": "tile",
      "statusGlyph": "none"
    }
  }
}

reputation shows the opener's points when the forum's list sends them on each row (authorPoints); a list that does not draws the rest of the line.

theme.forum.dates spells the list's dates:

Key

Values

What it does

style

absolute, relative, compact

"Oct 3" (unset), "3 hours ago", or "3h"

absoluteTitle

boolean

The full date and time in the tooltip. Unset: on

The Activity column shows when a thread last moved, not when it was posted. Closed and archived threads stay in the list (their pages are still served), marked by stateGlyphs. On a phone the dense table's numbers fold into a line under each title instead of scrolling sideways.

The front door's showVotes (default on) turns the card list's vote column off; the reply count stands in for it.

Theme components can target forum-topic-row-after-title, after each row's title in both anatomies.

The list's root carries data-forum-list and one data-fl-* attribute per key you set (data-fl-density, data-fl-avatars, data-fl-title-lines, data-fl-stats-style, …) for your own CSS. The meta line is [data-fl-meta-line], the end byline [data-fl-byline], a category tile [data-fl-cat-tile], the phone stats strip [data-fl-stats-strip].

Round-4 list keys (2026-10)

Each of these is a theme key (forum.list.*) that you can also set per category (forum.categoryOverrides.<slug>.list) and per front-page feed (the spaceFeed block's listPatch). The most specific one wins: the block, then the category, then the space theme, then the look.

Key

Values

Unset

What it does

anatomy

table, cards, rows

follows the layout preset (Dense list = table, otherwise cards). The clean and board looks on the default arrangement now default to table; cards and qa default to cards

table: the Replies / Views / Activity table. cards: the card list. rows: one ruled line per topic from 640px, with the title at the start and author · age and the reply count at the end; on a phone it stacks like a card. Works under every look. A feed block's explicit layout: "rows" still draws the table

stats.show

box, strip, both, none

the box when stats.fields is named (or a look such as qa names it), plus the phone strip when stats.phone: "strip"

strip: the one-line stats strip above every title at every width, with no box. both: box and strip. none: no stats anywhere, including stats a look such as qa sets

stats.width

64–160 (px)

4.5rem minimum

The stats column's width; numbers are right-aligned in it. The table column is that width plus a 16px gap before the title (the gap now always applies)

tagsPosition

adds inline

the table puts tags after the category badge; cards put them after the excerpt

inline: on the title's badge line, in both anatomies. In the table, meta puts them on a muted line of their own and after-excerpt under the excerpt

phone

{avatar: boolean, replies: "number" | "none"}

both shown

Below 640px: avatar: false drops the opener's face from each row, and replies: "none" drops the reply count. Both anatomies

pinPlacement

inline, above

inline

Below 1024px: inline puts the pin before the title's first word (now 640–1024px too, not only on phones). above puts it on its own line over the title

meta counts

—

—

With any stats showing, the meta line drops its replies/views words. Round 5 moved this choice to its own key, byline.counts (below). The front door resolves showCounts to true by default, so that toggle could never mean "keep them"

{
  "forum": {
    "list": {
      "anatomy": "table",
      "stats": { "show": "strip", "fields": ["votes", "answers"], "width": 96 },
      "tagsPosition": "inline",
      "phone": { "avatar": false, "replies": "number" },
      "pinPlacement": "above"
    }
  }
}

Bug fixes with no key: the table always writes its column widths (<colgroup>), so header.show: false no longer lets the title column collapse. Row rules in both anatomies use the theme's --line, so paletteDark.line applies.

Per category — forum.categoryOverrides. Up to 50 entries, keyed by category slug: { "<slug>": { "list": {…}, "categoryPage": {…} } }. The entry's keys are merged over the space's on that category's page (c/<slug>) and on the front-page feeds kept to it (filter.category: "$current"). Inside categoryPage, banner and filters merge field by field. A slug that names no category does nothing.

{
  "forum": {
    "list": { "anatomy": "table" },
    "categoryOverrides": {
      "announcements": { "list": { "anatomy": "cards", "excerpt": "all" } },
      "help": { "list": { "stats": { "show": "box" } }, "categoryPage": { "banner": { "align": "center" } } }
    }
  }
}

Round-5 list keys (2026-10)

These are theme keys (forum.list.*). You can also set them per category (categoryOverrides.<slug>.list) and per feed block (spaceFeed.listPatch). The FORUM default layer sets none of them.

Key

Values

Unset

What it does

byline

"none", "end", or {at, counts}

at: none; counts: auto

at is the round-3 choice (end: avatar · name · "asked …" at the row's end). counts controls the replies/views words under the title. auto drops them wherever the row draws stats (a box or a strip) that already show them. show keeps them anyway, and hide never draws them. In the table, counts also controls the views on the phone line and the reply number at the end of a phone row. Works in both anatomies

phone.avatarSize

16–48 (px)

the list's avatarSize (24, or 28 on a card)

The size of the faces on a phone row (below 640px): the opener's face at the start of the row, and any poster stack

phone.titleLines

1–3, or none

titleLines decides (the FORUM layer: 3 on a phone)

How many lines a title wraps to on a phone row (below 640px) before it is clamped. none: never clamped. Wins over titleLines.sm

phone.dividers

boolean

drawn

The rule between rows below 640px. false: none, the rows separated by their own padding. true: drawn even where a look took it away

header.labels

{topic?, posters?, replies?, views?, likes?, activity?}, ≤ 24 characters each

the built-in words

The table's column headings. "" leaves that heading blank and keeps the column

{
  "forum": {
    "list": {
      "byline": { "at": "end", "counts": "show" },
      "phone": { "avatarSize": 40 },
      "header": { "labels": { "topic": "Thread", "posters": "", "activity": "Last post" } }
    }
  }
}

Bug fixes with no key:

  • Counts appear once. byline.counts: "auto" now really is the default. Before, a Q&A row showed its replies and views twice: once in the stats and again on the line under the title.

  • Status glyphs stay with the title. The solved, closed, archived and saved glyphs sit inside the title's own text, in both anatomies and at every width. They no longer end up alone on a line above a long title that wrapped.

Front page

theme.forum.front — the filter row over the topic list, and the board panel around it. The row is the forum's, not the front page's: a category page draws the same one.

{
  "forum": {
    "front": {
      "filters": {
        "items": ["latest", "top", "solved", "unanswered", "categories"],
        "links": ["rss", "leaderboard"],
        "custom": [{ "label": "Guidelines", "href": "/guidelines", "icon": "book" }],
        "style": "underline",
        "pickers": ["category", "tags"],
        "newTopic": true,
        "count": true,
        "activeColor": "#4f46e5"
      },
      "panel": { "background": "#ffffff", "backgroundDark": "#111827", "radius": 12, "padding": 16, "page": "#f5f5f4" }
    }
  }
}

Key

What it does

filters.items

The tabs, in order, from latest, hot, top, new, unread, solved, unanswered, bountied, categories, bookmarks. Unset: Latest, Hot, New, Unread, Top, Solved, Unanswered. new, unread and bookmarks show only to a signed-in reader.

landing view

The first tab is what a bare visit to the front page shows: hot/top sort it, solved/unanswered filter it, new/unread apply to a signed-in reader (a signed-out reader gets Latest). Latest is then the ?sort=latest tab. categories and bookmarks first only order the row — they never redirect.

filters.links

The links at the row's end, in order, from rss, members, leaderboard, badges, about, bookmarks, markRead. Unset: mark read, bookmarks, RSS, members, leaderboard, badges, about. [] draws none. leaderboard still follows the front door's showLeaderboardLink.

filters.custom

Up to six of your own links after them: {label, href, icon} (icon a glyph name or an image).

filters.style

pills (today), underline (a rule under the active tab), segmented (one track, the active tab raised).

filters.pickers

Dropdowns before the tabs: category (the categories, nested), tags (the 20 most-used), status (Solved / Unanswered — those two tabs then leave the row). They open with no script.

filters.newTopic

A New topic button at the row's end; its composer opens under the row. Signed out it is the sign-in action, with the same words. Pair it with the front door's showIdentity: false to drop the identity band.

filters.count

"N topics" at the row's start, when the topic list reports its total.

filters.activeColor, activeColorDark

The active tab's colour (#hex). Unset: the brand.

panel

The filter row and the topic list as one panel: background/backgroundDark, border/borderDark, radius (0–32), padding (0–48), and page/pageDark — the page behind it. A colour object does not fit the front door's switches, which is why the panel lives here.

categories

Where the GENERATED front page puts its category rail: before the topics, after them, aside (a right column 20rem wide from 1024px — the categories as a compact link list — and after the list below that width) or none. A front page you composed is untouched. Unset: before (today's).

welcome

show: the generated front page opens on the welcome band — the forum's name, centred, over a large search (the separate search band then steps aside). auto: the same once the forum has four or more top-level categories. Unset: hide.

align

start: beside the forum sidebar, the page starts at the sidebar's edge (plus the shell's gap) instead of centring in what is left. Unset: center.

rail

A second rail beside the topic list, from 1280px (not drawn below it): {show, width, items}. width 220–360 px (unset 280); show: false turns a configured rail off. Up to five items, each {kind, title, period, body, tone, limit}: leaderboard (the forum's top people — period week/month/all, limit 1–10), tags (its most used tags), solved (its recently solved topics; on a category page, that category's), or box (your own body in markdown, up to 2000 characters, through the page renderer). tone: "note" paints a box pale yellow (dark-aware). Drawn on the front page's list and on c/<slug> (unless the category page names its own rail).

rail[].group

boolean (on a box)

{
  "forum": {
    "front": {
      "categories": "aside",
      "welcome": "auto",
      "align": "start",
      "rail": {
        "width": 300,
        "items": [
          { "kind": "leaderboard", "title": "Most helpful", "period": "month" },
          { "kind": "box", "title": "Featured on Meta", "tone": "note", "body": "- [Our new tagging policy](/f/meta/tag-policy)" }
        ]
      }
    }
  }
}

The front door (PUT …/preset, frontDoor) adds two switches for a community: showSearch (the search band; it was tied to showFilters, and an owner who turned the filters off keeps the search off too) and showIdentity (who is reading and the New topic box). Both default on. A wrong value is now a 422 that names the values the key takes (badgeStyle must be one of pill, outline, text), not "not a setting".

Round-4 front-page keys (2026-10)

All are theme keys under forum. (scope: the forum space, or a site theme for its forums). categoryPage.* keys can also be set per category through categoryOverrides.

Key

Values

Unset

What it does

front.filters.newTopic

true/false, or {show, placement: "end" | "start"}

true under the forum default

placement: "start" puts New topic first in the row instead of at its end

front.filters.pickerStyle

button, crumb

button

crumb: the pickers read as bare "categories › tags › all ›" text, with a rule before the tabs

front.filters.linksFit

line, wrap

line

From 640px the end links (RSS, Members, Badges, About…) stay on the tabs' line: the first linksInline are drawn inline and the rest go behind a "More" menu. wrap lets them wrap onto a second line, which is how the row drew before round 4

front.filters.linksInline

0–7

2

How many end links stay inline under linksFit: "line"

front.panel.scope

any of the forum page kinds (as sidebar.pages)

["front"]

The pages that wear the panel. On front it wraps the filter row and the list, as before. On every other page the whole page column becomes one card on the panel's page tint

front.panel.padding

0–48, or {sm, lg}

16

sm applies below 640px and lg from 640px. Example: { "sm": 4, "lg": 24 }

front.rail.items[].group

boolean

off

Joins a box to the one above it: one panel with a rule between them

front.categoriesLayout

rows, tiles, grid, table

the look's choice

The generated home's category band, under any look. Unset, it is tiles when three or more top-level categories each have a logo or an image icon, and ruled rows otherwise. Tiles never go into the aside rail; they sit before the topics

layout.topGap

{phone, desktop} in px (0–96 / 0–128)

16 / 24

The space between whatever sits above a forum page (header, banner, sub-nav, the phone Menu row) and its first line, on every forum page. The column's own top and the first element's top margin are folded into it. phone is below 640px. A home's front.paddingTop still wins there

pageHead.placement

column, above-sidebar

column

above-sidebar: from 1024px the front page's title, a category's H1, /categories' title row and the topic header span the page above the forum sidebar, with a rule under them, at their natural height (a breadcrumb, a long title and the topic's meta line all show). Only on pages where the sidebar is drawn; elsewhere the head stays in the column, ruled. It needs a visible title (layout.indexHeader: "show", or categoryPage.title: true)

{
  "forum": {
    "front": {
      "filters": {
        "newTopic": { "placement": "start" },
        "pickers": ["category", "tags"],
        "pickerStyle": "crumb",
        "linksInline": 1
      },
      "panel": { "background": "#ffffff", "page": "#f3f4f6", "padding": { "sm": 4, "lg": 24 }, "scope": ["front", "topic", "categories", "category"] },
      "rail": {
        "items": [
          { "kind": "box", "title": "The Overflow Blog", "tone": "note", "body": "- [Shipping faster](/b/blog/shipping)" },
          { "kind": "box", "title": "Featured on Meta", "tone": "note", "group": true, "body": "- [New tag policy](/f/meta/tags)" }
        ]
      },
      "categoriesLayout": "tiles"
    },
    "pageHead": { "placement": "above-sidebar" }
  }
}

Fixes with no key:

  • Category logos. On the generated home, a forum whose top-level categories all have a logo now gets 3-up logo tiles before the topics, and the welcome band when there are four or more categories (front.welcome unset). This applies under clean as well as cards. Wherever a category has a logo, the sidebar and the home's aside rows draw the logo (20px) instead of a colour square.

  • Top spacing. A page that opens on the search box, the filter row or the list starts 24px under the header, not about 70px.

  • Phone search. A wide searchWidth on a search band stays inside the screen on a phone.

  • Categories tab on the landing view. When a bare visit opens on a showOn: "landing" categories block, the Categories tab is the current tab.

Round-5 front-page keys (2026-10)

These are theme keys under forum.front (scope: the forum space, or a site theme for its forums). The FORUM default layer sets only categories: "auto".

Key

Values

Unset

What it does

categories

adds auto

before (the FORUM default layer sets auto)

auto is decided on the server. It is none where the forum sidebar already draws a CATEGORIES section on the front page, so the categories are listed once and the topic column keeps its width. Otherwise it is aside. Set aside to bring the aside back beside a sidebar

asideMinWidth

1100–1920 (px)

drawn wherever the aside draws (1024px up)

Below this viewport width the generated home's categories aside is hidden by a media query and the list takes the whole row. Nothing moves after the first paint

filters.phone

scroll, select, wrap

scroll

Below 640px. scroll: the tabs are one line that scrolls sideways, with a fade at its end. More comes after the last tab and never covers it, and New topic stays at the end of the row as a compact button. Any pickers take the line above. select: the tabs become one native select named for the current view, and choosing an option goes to that tab's address. The pickers keep their row. wrap: the tabs wrap onto as many lines as they need

filters.size

12–20 (px), or a typography.levels name

as today (14px tabs)

The filter row's text: tabs, pickers and the phone select

filters.pickerInk, filters.pickerInkDark

hex colours

as today (the button's ink, or muted for crumb)

The pickers' ink, light and dark. Works with both picker styles

filters.labels

{latest?, hot?, top?, new?, unread?, solved?, unanswered?, bountied?, categories?, bookmarks?}, 1–24 characters each

the built-in names

A tab's own words. Its link and its place in the row stay the same

panel.width

column, frame

column

frame: on the topic page (with topic in panel.scope), the panel card spans the whole list frame, with the post column, its rail and the beside timeline inside it. column is the post column alone, as before

panel.maxWidth

720–1600 (px)

1152 (the board width, 72rem)

How wide the frame card may grow on the topic page. Example: 1250

filters.phonePickers

boolean

drawn

false: no category, tag or status pickers below 640px. The tabs keep the row; the pickers still draw from 640px

{
  "forum": {
    "front": {
      "categories": "aside",
      "asideMinWidth": 1280,
      "filters": {
        "phone": "select",
        "size": 17,
        "pickerInk": "#2563eb",
        "pickerInkDark": "#93c5fd",
        "labels": { "latest": "Active", "new": "Recent" }
      },
      "panel": { "scope": ["front", "topic"], "width": "frame" }
    }
  }
}

front.categories: "auto" and the sidebar sections it reads are resolved on the server, so the home is drawn once, with its final column count.

Categories, tags and sidebar

Every key is optional; unset draws what the forum drew before.

On every forum page (topics, a category, /categories, /tags, a topic, a profile) when enabled is true.

Key

Values

Unset

enabled

true / false

off

position

start, end

start

sections

up to four of community, categories, tags, custom, in order

community, categories, tags

categories

category slugs to list, in order

every top-level category, with its subcategories

tags

tags to list, in order

the ten most used

custom, customTitle

up to 20 {label, href, icon?} links and their heading

none

showCounts

show each category's topic count

off

collapsible

subcategories fold under their parent

always shown

icons

draw each category's mark

on

width

180–320 (px)

240

background, backgroundDark

#hex

none

activeColor, activeColorDark

brand, ink, muted, category or #hex / #hex

the brand

footer

["themeToggle"] adds the light/dark switch, in the header toggle's own look (chrome.themeToggle.style)

none

tagsLayout

inline (chips), list (one tag per row with a tag glyph, like the category rows)

inline

headings

false drops the COMMUNITY / CATEGORIES / TAGS / LINKS labels (each list keeps its accessible name)

shown

fullHeight

true: from 1024px a sticky column from under the header to the window's foot, ruled from the page

off

phone

drawer (an off-canvas sheet), top (an inline menu), hide

drawer

Below the desktop width the sidebar folds into a Menu button — except under a header that has its own menu and no sidebar toggle (chrome.sidebarToggle): there the header's menu button opens the forum drawer, with the header's own links at its foot, so a phone shows one menu control and the page starts right under the header. A header button carrying data-forum-sidebar-opener (the header's sidebar toggle) opens the drawer too, as does a forumulate:forum-sidebar event on window. With position: "end", theme components targeting the forum side column stack under the sidebar.

categoriesPage — the /categories index

Key

Values

Unset

layout

list, grid, boxes, boxes-with-subcategories, table (one ruled table: a bar in each category's colour, the name over its description, the Topics count rolled up over subcategories, the subcategories as badge chips in the front door's badgeStyle)

list (the card stack)

beside

none, latest, top — a column of topics beside the categories, as compact rows: the opener's face, the whole title, the reply count over a short age

none

filters

true draws the forum's filter row above the categories, with the Categories tab current

off

columns

1–4, for grid and boxes

3

fields

which of description, counts, latest, subcategories, unread, logo a card draws

description, counts, latest, unread, logo

categoryPage — a category's own page (c/<slug>)

Key

Values

Unset

title

text with {category}, {forum}, {parent}; or true (the H1 is drawn even where layout.indexHeader hides index titles — the forum default) / false (kept for screen readers and search only)

{forum} — {category}, following indexHeader

banner

auto, color (filled with the category colour), image (the background), none; or {style, search} — search: true puts the forum's search inside the banner (under the H1 when no banner is drawn)

auto: a banner only with a logo or background

list

frontPage: a spaceFeed you kept to category views (showOn: "category" or "both") with filter.category: "$current" IS the list, and the native one is not drawn (one list, not two). Without such a feed the native list stays

native

rail

up to three of solved (this category's solved topics), tags (trending tags), related (sibling and child categories) — a right column from 1280px; it replaces front.rail here

none

stats

true shows "N topics · N replies · N this week"

off

subscribe

true shows a follow button to signed-in readers

off

subcategories

rows, boxes, chips, table, none (a category's own subcategoryListStyle wins)

the old strip

breadcrumb

false hides "forum › parent" above a subcategory

shown

liftDark

true raises a category colour that disappears on the dark page to 3:1 against it, everywhere a category is drawn

off

A category's own colorDark always wins over the lift.

tagsPage — the /tags index

Key

Values

Unset

sort

count, name (readers can switch)

count

groups

true sections the tags by tag group

one list

descriptions

true shows each tag's description

off

tagStyle and categories

  • tagStyle — how tags look on forum pages: chip, outline, outline-mono, text, eyebrow (the blog's tag looks, in each tag's own colours), or plain (muted words). Unset, a tag follows the front door's badge style.

  • categories.icons — true draws a mark beside every category name (its icon, else its emoji, else a colour square), even for a category with no style.

Badge styles

The front door's badgeStyle setting takes three more values on a forum: dot (a coloured dot before muted text), square (a coloured square) and bar (a coloured bar on the leading edge), beside pill, outline and text.

Round-4 keys (2026-10)

Header links only in the ☰ menu. chrome.headerLinks[].placement (bar unset, menu, both) puts a link in the menu alone — the ☰ is then drawn at every width — or in the row and the menu. A ☰ with nothing to list is never drawn. See Theming, advanced.

sidebar (theme)

Key

Values

Unset

What it does

pages

any of front, category, categories, topic, profile, search, tags, tag, members, leaderboard, badges, about, account (bookmarks, notifications, preferences, my activity), rooms

every page

The pages that draw the sidebar. On the rest the page takes the full width

headings

also an array of community, categories, tags, custom

shown

The sections that keep their heading. ["categories", "tags"] drops only COMMUNITY

sectionFold

boolean

off

The CATEGORIES / TAGS headings fold behind a chevron. Each section starts open, and the reader's choice is remembered in their browser

allLinks

boolean

off

Adds an "All categories" row (to /categories) and an "All tags" row (to /tags, in place of "more")

activeStyle

fill, ink, bar

fill

The current row: a tinted row (fill), the active colour on the words only (ink), or a 3px bar at the row's start (bar)

{ "forum": { "sidebar": { "enabled": true, "pages": ["front", "category", "categories"], "headings": ["categories", "tags"], "sectionFold": true, "allLinks": true, "activeStyle": "ink" } } }

A sidebar link to the forum's root is now current only on the root itself, not on every forum page. Below 1024px the sidebar's background is no longer drawn as an empty band where the Menu button used to be.

categoriesPage (theme)

Key

Values

Unset

What it does

besideRow

{avatarSize: 16–48, meta: up to 4 of category, tags, replies, age, titleLines: 1–3}

today's face, an unclamped title, replies over age

The rows of the beside column. category and tags draw on a line under the title

title

boolean

drawn

false hides the H1 and the "All topics" link. The H1 stays as screen-reader text

nameInk

muted, ink, category

ink

A category name's colour. category uses the category's own colour

nameSize

12–32 (px), or a type level (h1…h6, body, small, code)

today's size

A category name's size. A level name follows typography.levels

textSize

11–20 (px)

14

Descriptions and "Latest" lines on cards and rows. These were 12px before round 4

{ "forum": { "categoriesPage": { "layout": "table", "beside": "latest", "besideRow": { "avatarSize": 40, "meta": ["category", "tags"], "titleLines": 2 }, "title": false, "nameInk": "category", "nameSize": 16 } } }

In the grid layout, subcategory chips now sit inside each bordered card, under the description. The front page's categories table reads list.statusGlyph (with checkbox, open topics get an empty box), draws a mark before each name under categories.icons, spells ages with forum.dates, and shows each topic's reply count on a phone.

categoryPage (theme, or per category through categoryOverrides.<slug>.categoryPage)

Key

Values

Unset

What it does

banner.align

start, center

start

The banner's words, logo and search, centred

banner.width

content, full

content

full: the banner runs edge to edge across the window. With a forum sidebar (from 1024px) it fills its own column instead

banner.searchWidth

200–960 (px)

the banner's width

The width of the banner's search box

filters.placement

below-title, title-end

below-title

title-end: the filter row sits at the end of the H1's line, and wraps under it when there is no room

A category page with its own rail now splits the forum's frame into the list plus an 18rem rail, so it ends at the header's right edge instead of running 24rem past it.

{ "forum": { "categoryPage": { "banner": { "style": "color", "search": true, "align": "center", "width": "full", "searchWidth": 560 }, "filters": { "placement": "title-end" }, "rail": ["solved", "related"] } } }

Round-5 keys (2026-10)

Unset, every key below draws the page it drew before, except the one the FORUM defaults layer sets (the "Layer" column).

sidebar (theme)

Key

Values

Unset

Layer

What it does

subcategories

flat, nested

flat

nested

nested: only the top level is listed. A parent with children gets a caret and opens to them. The current category's parent (or the category itself) is open in the first paint, with the current row marked. flat: every child drawn under its parent, as before. An explicit categories list is always flat

phoneHeaderLinks

boolean

on

—

The phone drawer (and the top disclosure) ends with the header's own links: the site's when the forum is in a site, else chrome.headerLinks. A dropdown opens as an accordion. Hidden from 1024px, where the header sits beside the sidebar. false: the drawer lists only its own sections

{ "forum": { "sidebar": { "enabled": true, "subcategories": "nested", "phoneHeaderLinks": false } } }

categoriesPage (theme)

Key

Values

Unset

What it does

descSize

11–20 (px), or a type level (h1…h6, body, small, code)

textSize, else 14px

A category's description, in every layout (list, grid, boxes, table)

fields

also perWeek

—

Named only: the table's Topics column reads "14 / week" for a category with a weekly rate, and the total for one without. Never on unless named

besideRow.icons

boolean

on

false: the beside column's reply count is a bare number, with no speech-bubble glyph

besideRow.avatarShape

circle, square

circle

The beside column's faces. square gives them 4px corners

headCase

upper, none

the beside heading upper case, the table head as before

none: "Latest" and the table head in their own case. upper: both upper case

phoneCounts.show

total, perWeek, both

the rate when fields names perWeek, else "N topics"

The table layout's phone count line (below 640px). total: "120 topics"; perWeek: "3 / week" (the total for a category with no rate yet); both: "120 | 3 / week". Setting it draws the line even with counts off; the desktop Topics column is unchanged

phoneCounts.at

name, end

end

Where that line sits: name straight under the category name (over the description), end after the description and subcategories

{ "forum": { "categoriesPage": { "layout": "table", "fields": ["description", "subcategories", "perWeek"], "descSize": 15, "beside": "latest", "besideRow": { "icons": false }, "headCase": "none" } } }

categoryPage (theme, or per category through categoryOverrides.<slug>.categoryPage)

Key

Values

Unset

What it does

banner.width

also shell

the content column

A full-bleed band ABOVE the sidebar and the list, from the header's bottom edge, square and unbordered. Only when a banner is drawn (banner.style). A title-end filter row (filters.placement) stays in the column under it

breadcrumb

also none, above-banner, below-banner

the "Forum › Parent" crumb, on a subcategory only

above-banner / below-banner: the full "Home › Categories › Parent › Category" trail on every category, above or below the banner (or the H1). none (or false): no crumb

subscribeStyle

link, button

link

With subscribe on, button gives a signed-out reader an outlined "Subscribe" button that goes to sign-in and back. A signed-in reader keeps the notification-level button

{ "forum": { "categoryPage": { "banner": { "style": "color", "width": "shell", "align": "center" }, "breadcrumb": "above-banner", "stats": true, "subscribe": true, "subscribeStyle": "button" } } }

Mix and match: list, front page and categories

Every key above works under every look. A look (clean, board, cards, qa, paper) only sets defaults for individual keys. No renderer checks which look you chose, so any key you set wins over the look, field by field. Each key also has a scope, the place it can be set:

Scope

Where

Example

theme

theme.forum.* on the space, or a site theme for its forums

forum.list.anatomy

category

theme.forum.categoryOverrides.<slug> (list, categoryPage)

the Announcements category as cards

block

a front-page block's own attributes (spaceFeed.listPatch, spaceFeed.card.*, spaceCategories.layout)

one feed on the home as a table

The most specific scope wins: block, then category, then theme, then the look. None of the three combinations below is a replica of another forum.

1. A board table under the cards look, with Q&A stats only in Help. The cards look supplies its thumbnails, avatars and post cards. The list is the table, and the help category gets a votes/answers strip.

{
  "forum": {
    "look": "cards",
    "list": { "anatomy": "table", "columns": ["posters", "replies", "activity"], "header": { "show": false } },
    "categoryOverrides": {
      "help": { "list": { "stats": { "show": "strip", "fields": ["votes", "answers"] } } }
    },
    "front": { "categoriesLayout": "tiles", "categories": "before" }
  }
}

2. The qa look with its stats switched off, on a white card on grey. qa still sorts replies by votes and pins the accepted answer. The list is a plain rows list, and every page sits in the panel card.

{
  "forum": {
    "look": "qa",
    "list": { "anatomy": "rows", "stats": { "show": "none" }, "phone": { "avatar": false } },
    "front": {
      "panel": { "background": "#ffffff", "page": "#f1f5f9", "radius": 16, "padding": { "sm": 4, "lg": 24 }, "scope": ["front", "category", "categories", "topic"] },
      "filters": { "style": "underline", "pickerStyle": "crumb", "pickers": ["category", "tags"], "newTopic": { "placement": "start" } }
    }
  }
}

3. paper type and colours with a modern sidebar and a full-width head. Nothing from clean comes in except what is named here.

{
  "forum": {
    "look": "paper",
    "sidebar": { "enabled": true, "pages": ["front", "category"], "headings": ["categories"], "activeStyle": "bar", "allLinks": true },
    "pageHead": { "placement": "above-sidebar" },
    "categoryPage": { "title": true, "banner": { "style": "color", "align": "center" }, "filters": { "placement": "title-end" } },
    "categoriesPage": { "layout": "grid", "nameInk": "category", "textSize": 15 }
  }
}

5. A cards-look forum with table heads in its own words and a select on phones. The cards look keeps its avatars and thumbnails. One category is a table with its own column heads, the list keeps its counts under a stats strip, and phones get the tabs as one select under a framed topic card.

{
  "forum": {
    "look": "cards",
    "list": {
      "stats": { "show": "strip", "fields": ["votes", "views"] },
      "byline": { "counts": "show" },
      "phone": { "avatarSize": 36 }
    },
    "categoryOverrides": {
      "releases": { "list": { "anatomy": "table", "header": { "labels": { "topic": "Release", "posters": "" } } } }
    },
    "front": {
      "categories": "auto",
      "asideMinWidth": 1360,
      "filters": { "phone": "select", "labels": { "hot": "Trending" }, "size": "small" },
      "panel": { "scope": ["topic"], "width": "frame" }
    }
  }
}

The building blocks this adds: list.byline.counts, list.phone.avatarSize, list.header.labels, front.categories: "auto", front.asideMinWidth, front.filters.phone, front.filters.size, front.filters.pickerInk, front.filters.labels and front.panel.width. Each works under every look, and a look only ever supplies a default for one of them.

4. A nested sidebar beside a shell-wide category band, under board. The board look keeps its table and underline tabs. Each category opens on a band across the whole page with the trail under it, the sidebar lists only the top level, and the /categories table reads weekly rates in sentence case.

{
  "forum": {
    "look": "board",
    "sidebar": { "enabled": true, "subcategories": "nested", "activeStyle": "bar", "phoneHeaderLinks": true },
    "categoryPage": { "banner": { "style": "image", "width": "shell" }, "breadcrumb": "below-banner", "subscribe": true, "subscribeStyle": "button" },
    "categoriesPage": { "layout": "table", "fields": ["description", "perWeek"], "descSize": "small", "headCase": "none", "beside": "top", "besideRow": { "icons": false } }
  }
}

Topic page

Two families: post (one post's anatomy) and topic (the page around the posts). Every key is optional; unset renders the topic page as it was.

{
  "forum": {
    "post": {
      "avatar": { "position": "gutter", "size": "md", "shape": "circle", "positionSm": "inline" },
      "date": "relative",
      "opChip": true,
      "staffAccent": "brand",
      "reactions": "both",
      "menu": ["like", "link", "reply"],
      "menuHidden": ["bookmark", "quote", "branch", "flag"],
      "actions": { "align": "end", "style": "icons" },
      "bodySize": "base",
      "headingScale": "body",
      "style": "card",
      "gapMarkers": 7,
      "reactionsRow": "inline",
      "badges": "hover",
      "acceptedStyle": "gutter-check",
      "header": "stacked",
      "bodySurface": "#f3f4f6",
      "bodySurfaceDark": "#1f2937",
      "quote": { "avatar": true }
    },
    "topic": {
      "breadcrumb": "trail",
      "map": { "show": "auto", "minReplies": 3, "style": "full" },
      "answerPlacement": "excerpt",
      "share": { "mode": "icon", "align": "end" },
      "actions": ["share", "bookmark"],
      "rail": ["related", "solved"],
      "replyBar": { "signedOut": "bar", "placement": ["after-op", "end"] },
      "align": "start",
      "timeline": { "placement": "beside-column", "phone": "pill" },
      "measure": 760
    }
  }
}

post

Key

Values

What it does

avatar.position

gutter · inline · none

The author's picture: a column left of the post, first in the header row, or none. Unset: no avatar.

avatar.positionSm

same

The same under 640px (unset: position).

avatar.size

sm (24) · md (45) · lg (56) · 20–64

Its size in px.

avatar.shape

circle · rounded · square

name

name · username · name-username · username-name

Which name the header shows; the second, muted.

name: "username-plain"

—

The handle without its @ ("wrenh").

date

absolute · relative

The header date ("3 hr. ago"). It is always the post's #post-N permalink, with the full time on hover.

number

boolean

Prefix the date with #N.

staffAccent

hex · brand · success · ink · muted · none

Owners, admins and moderators get a shield and this colour on their name; system lines take it too.

opChip

boolean

An "Author" chip on the topic starter's replies.

newMemberChip

boolean

A "New member" chip on trust-level-0 authors.

reactions

palette · like · both

The emoji chips (today), one heart bound to 👍 in the action row, or both (the heart owns 👍, the palette the rest).

menu

list of like reply quote bookmark link branch flag edit history

The action row, in order.

menuHidden

same

Items folded into the "…" menu.

actions.align / actions.style

start·end / text·icons

Where the row sits and whether it shows labels (icons keep them for screen readers).

bodySize

sm · base · lg · 13–20

Post text size (unset: 14px). A typography.levels.body.size you set still wins.

headingScale

page · body

body sizes headings inside a post relative to the post (1.25em / 1.1em / 1em), not the page.

quoteStyle

plain · bar · card

How a quote of another post looks; card puts "Name said:" on top.

style

rule · card · flat

A ruled list (today), bordered cards, or no rules.

byline

top · card-end

The author line at the top, or a Stack Overflow user card at the end of the post.

voteStyle

none · inline · gutter

▲ score ▼: in the action row or a column left of the post. Answers always; the opening post only on a question.

status

boolean

The author's status emoji and message after their name (when members may set one).

flair

chip · avatar-overlay

Group flair as a chip (today) or a disc on the avatar.

reputation

boolean

Points and gold/silver/bronze badge counts. true: in the header (and the card-end byline); false: nowhere. Unset: in the card-end byline only, never in the header.

gapMarkers

1–365 · false

"N days later" between posts that far apart (chronological order only).

reactionsRow

separate · inline

Where the emoji chips and the add-reaction button go: their own row under the actions (unset), or inside the action row, chips first.

badges

always · hover · none

The badge glyphs after a name: always (unset), only while the header is hovered or focused (never on a touch screen), or never.

acceptedStyle

box · none · gutter-check

The accepted reply: a green frame and chip (unset), drawn as a plain post, or a green check under the vote gutter (in the header when the post has no gutter).

header

inline · stacked

The author line as one row (unset), or the name over "role • date" (the role is the author's title, else Staff/Moderator).

bodySurface / bodySurfaceDark

hex

A rounded bubble behind the post text, in light / dark mode.

quote.avatar

boolean

The quoted author's face before the name in a quote's head (their picture, else their initials disc).

avatar.sizeSm

20–64

The face's size under 640px. Unset: 32. Example: "avatar": { "position": "inline", "size": "sm", "sizeSm": 28 }.

likeChip

auto · show · hide

The 👍 chip (and like heart) beside a vote. auto (unset): hidden where a ▲ score is drawn — the score is that reaction; show: drawn anyway; hide: never a 👍 chip. Example: "likeChip": "show" with "voteStyle": "gutter" draws both.

reactionPicker

auto · show · hide

The add-reaction (smiley) button. show (unset): always; hide: no picker, every palette reaction is a fixed toggle chip; auto: ONE reaction control per post — the like heart alone when the space has one reaction enabled (it toggles that reaction), the picker (chips + smiley) alone otherwise; with reactions: "like" (no chips at all) the heart stays. The FORUM defaults use auto. Example: "reactionPicker": "hide".

topic

Key

Values

What it does

header.category / header.tags

chip·text·none / chips·none

The category link (with its colour) and the tag chips under the title. text: the category's name in its own colour, no chip.

header.kindChip

boolean

false hides the Question/Discussion chip from readers (moderators keep it).

header.statusGlyph

boolean · pill · checkbox

A check before a solved question's title, a lock before a closed one. checkbox: the lock, then the question's checkbox — ticked in the solved colour once answered, empty until then.

header.stateChip

boolean

false drops the "Closed"/"Archived" chip from the meta line (leave the lock to statusGlyph). Unset: drawn.

header.solvedPill

{ text: "solved"·"answeredBy", placement: "before-title"·"meta"·"meta-start", style: "tint"·"outline"·"solid" }

ONE solved pill, replacing statusGlyph: "pill" and the solved meta item: "✓ Solved" or "✓ Answered by {name}", before the title, at the meta line's end (meta), or first on it (meta-start, before the facts or the sentence), tinted (unset), outlined, or solid (R5) — filled with the solved colour, the page's background as ink. Example: { "text": "answeredBy", "placement": "meta", "style": "outline" }; { "placement": "before-title", "style": "solid" }. Theme scope; no layer default.

header.solvedColor

hex · success · brand

The solved pill's own colour. Unset: answerColor.

header.cta

{ label: text ≤40, href }

A button at the end of the title row ("Ask Question"). href: an http(s) URL, a /path on the site, or a path under the forum ("c/questions" → that category's page, where its New topic button is). Example: { "label": "Ask Question", "href": "c/questions" }.

header.cta.phone

inline · below · above · hide

The button under 640px: below (unset, as before) wraps under the title; inline keeps it at the end of the title row, the title wrapping beside it; above puts it on its own line over the title, at the end edge (Stack Overflow's phone page); hide drops it on a phone. Theme scope; no layer default. Example: "cta": { "label": "Ask Question", "href": "c/questions", "phone": "inline" }.

header.metaStyle

list · sentence

The meta line as a list of facts (unset) or one sentence: "tvoronov asked this question in Q&A" ("started this discussion in" for a discussion; the category part drops when there is none), in the reader's language. The author is named as post.name names them. The sentence carries the category link, so the category chip is not drawn beside it (a moderator keeps theirs — it is their control). Theme scope; no layer default. Example: "metaStyle": "sentence".

header.metaTemplate

text, ≤80

The sentence in your own words, with the tokens {author}, {category} and {age} (the topic's date). Read only with metaStyle: "sentence". Theme scope. Example: "metaTemplate": "{author} opened this {age} in {category}".

header.number

boolean

The topic's number after the title, muted: "#24636". It is the topic's number — an imported topic carries its source number in its fields (fields.number); a topic with none draws nothing. Theme scope; no layer default. Example: "number": true.

header.meta item solved

—

A green "Solved" pill after the meta line on a solved question. statusGlyph: "pill" puts the same pill before the title instead of the check.

header.meta

list of author replies views likes asked modified activity

The meta line, in order. Unset: author · replies · views.

breadcrumb

back · trail · none

"All topics" (today), Forum › Parent › Category, or nothing.

breadcrumbRoot

text, ≤40

The trail's first crumb (unset: "Forum") — "Community › Help". The JSON-LD trail uses it too.

map.show

never · auto · always

The topic map under the first post; auto waits for map.minReplies (0–100).

map.style

full · strip · stacked

Discourse's boxed stats, one line ("322 views · 9 likes · 8 users") with an avatar stack, or stacked — the box with each stat a big number over a small lowercase label ("322" / "views"), centred. Theme or category scope (byCategory[…].map); no layer default. Example: "map": { "show": "always", "style": "stacked" }.

map.fullFrom

1–200

The full box only from this many replies; below it, the strip. Unset: map.style always.

replySort

oldest · newest · votes

Reply order. Unset: chronological; a question keeps accepted-then-votes.

sortControl

boolean

Oldest / Newest / Votes links above the replies (?replies=).

answerPlacement

pinned-first · inline · excerpt · excerpt-in-post

A solved question's answer: first (today), in place, in place plus a "Solved by" card under the question, or that card INSIDE the opening post, before its actions.

answerStyle

outline · filled · header

The "Solved by" card: an outline with a tinted head (unset), a filled tint with no outline, or header — a solid bar reading "✓ SOLVED" over a plain bordered body. Every style takes answerColor.

answerBody

excerpt · full

What the "Solved by" card says: the answer's first 300 characters (unset), or full — the whole accepted answer, rendered like the post, in a fold that is open on load and that the reader can close (no script). Works with every answerStyle.

answerColor / answerColorDark

hex · success · brand / hex

The colour of EVERY solved marker: the card, the Solved pill, the accepted reply's frame and chip, the gutter check, the list's solved check. Unset: success — the palette's success colour, else #15803d (#4ade80 in dark mode), never the brand. answerColorDark: a hex for dark mode (unset: answerColor). Example: "answerColor": "brand".

acceptedBanner

boolean

The "Accepted answer · jump to it" card under the head (answerPlacement pinned-first). false drops it; true draws it, and the accepted reply is then drawn plain. Unset: drawn only when the reply carries no marker of its own (post.acceptedStyle: "none") — see "One marker per accepted answer" below.

share.mode

row · menu · icon · none

The share control; networks, frame, label are the blog's post.share keys.

share.frame

circle · square · none · box

The row's icon frame, as on a post; box: the share glyph in a bordered 32px square. Example: "share": { "mode": "icon", "frame": "box" }.

share.align

start · end

Where the share control sits in the actions row (unset: in its place in actions).

actions

list of share translate bookmark notify

The actions row under the title, in order; [] draws no row. Unset: share, translate, bookmark, notify. Whether Translate shows is translate.

actionsPlacement

row · meta

The actions as their own row under the head (unset), or as icons at the end of the meta line — no ~50px band. The FORUM defaults use meta. Example: "actionsPlacement": "meta".

translate

provider · always · never

Translate's visibility. provider (unset): only when the org has connected a translation provider (Settings → DeepL); always: drawn anyway — staff are told to connect a provider if it fails; never: not drawn, provider or not.

watch.style

link · button

The Watch / Subscribe control as a plain link or a solid button. Unset: an outlined button.

rail

none · meta · related · timeline, or a list of 1–6 of meta related solved timeline and boxes

A sticky side rail: facts (category, tags, participants, dates), related topics, or the timeline. A list stacks its sections in order (each section once); solved lists solved topics in the topic's category, each with a check (a related topic known to be solved gets one too). A box is the list rail's: { "kind": "box", "title": "Featured", "body": "markdown", "tone": "note" } (note: pale yellow).

timeline.placement

viewport · beside-column · none

Today's right edge, just right of the column (≥1280px), or off.

timeline.phone

none · pill

A "3 / 40" pill on phones that jumps to a post.

timeline.minReplies / timeline.color

1–200 / ink

When it appears (unset: 5) and its fill.

timeline.dateStyle

mono-full · short · day-month

The end dates: "Oct 3, 2025" in mono (unset), "Oct 2025" in the page's sans, as Discourse prints them, or day-month — "Sep 20" in the page's sans, in the reader's locale order ("20 sept."). Theme scope; no layer default. Example: "timeline": { "dateStyle": "day-month" }.

threading

flat · nested · comments

Per space. Unset: the org setting (community.enable_threaded_replies). comments is Q&A: replies with no parent are answers, the rest a compact comment list under the post they answer, and the box reads "Your answer".

summarize.show / summarize.minReplies

boolean / 1–500

A "Summarize this topic" button for signed-in readers, when the org has connected an AI provider. Summaries are cached per reply count.

measure

560–1000

The reading column in px.

align

center · start

Unset: against the forum sidebar where there is one at the start edge (≥1024px), centred otherwise. start: against the sidebar — or, with no sidebar, the page frame's start edge (the title lines up with the logo, inside layout.widths.max). center: centred, sidebar or not. The beside-column timeline follows the column.

titleSize

length, or { sm, md, lg }

The topic title's size. Unset: clamp(1.5rem, 1.1rem + 2vw, 2.25rem). A typography.levels.topicTitle size still wins. Example: "titleSize": { "sm": "20px", "lg": "40px" }.

titlePlacement

above · in-first-post · in-first-post-after-author

The title (with its meta line and chips) above the thread (unset), inside the opening post over its author line, or in-first-post-after-author — inside the opening post AFTER its author row: the author, then the Solved pill (a before-title pill gets its own line), then the title. The breadcrumb stays above. Theme scope; no layer default. Example: "titlePlacement": "in-first-post-after-author".

composer.signedOut

box · none

The "Sign in to join in" box at the thread's foot for a signed-out reader (unset: box). none: nothing — a replyBar is then their way in.

byCategory

{ "<category slug>": { … } }, ≤50

Per-category overrides of answerStyle, answerColor, answerColorDark, actions, map, rail, replyBar, header, share, timeline, threading and post (the posts' own keys), merged key by key over the space's ("replyBar": false takes the space's bar away). A subcategory with no entry takes its parent's. Keys are category slugs (c/<slug>). See "Mix and match".

replyBar.signedOut

bar · none

A "Leave a reply…" bar. bar: signed-out readers get it too, and it opens the space's sign-in in place (they come back to the topic); none: only members who can reply. Signed in, it opens the composer. Unset replyBar: no bar.

replyBar.placement

list of after-op after-answer end

Where the bar appears (unset: end). At end it replaces the composer's "Sign in to join in" box for signed-out readers.

related / suggested

boolean

false drops the related / suggested lists at the foot.

answersHeading

boolean

An "N Answers" heading over the replies (the sort control beside it).

Fixed for everyone, no key: the composer, presence line and the linked/related/suggested lists sit in the topic column (they used to run flush left); a topic's tags are drawn; an uncategorised topic no longer shows readers a "Category" chip; closed and archived topics carry a lock chip and say so where the reply box was; post anchors (#post-N) are each post's own number everywhere; a reply that arrives live looks like the same reply after a reload; staff see a whisper marked "Only staff see this".

Also fixed for everyone (round 3): "Translate" is drawn only when the org has connected a translation provider; a post that takes votes no longer shows a 👍 chip (or like heart) beside its ▲ score — the score is that reaction; the topic title scales with the screen (clamp(1.5rem, 1.1rem + 2vw, 2.25rem), line height 1.2) unless typography.levels.topicTitle or h1 sets a size; the meta line counts in your language ("1 view", "1 reply"); the topic map's stats are an even grid; the phone timeline pill sits bottom-right above the safe area, under a docked composer, and hides while you write.

Also for everyone (round 4): every solved marker — the "Solved by" card, the Solved pill, the accepted reply's frame and chip, the gutter check and the list's solved check — is the success green, not the brand (answerColor changes it, "brand" puts it back to the brand); the topic map lays its stats on equal columns, never one stat alone on a second row (7 on one row, or 4 + 3); beside a forum sidebar the topic column starts at the sidebar's edge instead of centring ~200px away (align: "center" keeps the centring); a post's face on a phone is 32px (post.avatar.sizeSm).

Also for everyone (round 5):

  • One marker per accepted answer. A solved question's accepted reply is marked by exactly one thing, whatever answerPlacement, acceptedBanner and post.acceptedStyle say. With excerpt / excerpt-in-post the "Solved by" card marks it and the reply is drawn plain. With inline the reply's own marker does. With pinned-first (unset) it is the reply's own marker (frame and chip, or the gutter check) and no "jump to it" bar — unless acceptedBanner: true or acceptedStyle: "none", in which case the bar marks it and the reply is plain.

  • The phone pill keeps out of the way. With timeline.phone: "pill", the thread keeps the pill's height plus its gaps free at its foot, and jumps to a post stop that far above the screen's bottom edge, so the pill never sits on a post's actions.

  • reactionPicker: "auto" is one control (see post.reactionPicker).

Mix and match

Every key on this page is a building block: it works under every look and next to every other key, and a look is only a PATCH of these same keys (see "Looks and defaults") — pick a look, then override any key it set, or skip the look and set the keys yourself. Scope, from widest to narrowest:

  • theme — theme.forum.post.* and theme.forum.topic.* apply to every topic in the space;

  • category — theme.forum.topic.byCategory["<slug>"] overrides answerStyle, answerColor, answerColorDark, actions, map, rail, replyBar, header, share, timeline, threading and post for one category's topics (a subcategory with no entry of its own takes its parent's);

  • page — a single topic's own customization is drawn over both: its custom CSS, theme colours, and its own forum keys (forum.post.*, forum.topic.*) under customization.theme. Another type's namespace is refused there with a 422 naming it.

Three combinations that are not any replica. Each is a complete PATCH /orgs/{orgId}/spaces/{spaceId}/theme body.

A board-style table for the list, a solid green "Solved" header card inside the question, Stack Overflow's checkbox in the title, and an "Ask Question" button beside it:

{
  "forum": {
    "list": { "avatars": "posters", "columns": ["posters", "replies", "views", "activity"] },
    "post": { "style": "card", "avatar": { "position": "gutter", "positionSm": "inline", "sizeSm": 32 } },
    "topic": {
      "answerPlacement": "excerpt-in-post",
      "answerStyle": "header",
      "header": {
        "statusGlyph": "checkbox",
        "category": "text",
        "cta": { "label": "Ask Question", "href": "c/questions" }
      },
      "actionsPlacement": "meta",
      "share": { "mode": "icon", "frame": "box" }
    }
  }
}

One space, two kinds of category: Help reads as Q&A (votes, the answered pill, the full map), Announcements as a broadcast (no map, no reply bar, no actions), and the solved colour is the brand everywhere:

{
  "forum": {
    "post": { "voteStyle": "gutter", "likeChip": "show", "reactionPicker": "auto" },
    "topic": {
      "answerColor": "brand",
      "answerPlacement": "excerpt",
      "header": { "solvedPill": { "text": "answeredBy", "placement": "meta", "style": "outline" } },
      "map": { "show": "auto", "style": "full", "fullFrom": 5 },
      "replyBar": { "signedOut": "bar", "placement": ["after-answer", "end"] },
      "byCategory": {
        "help": { "answerStyle": "header", "rail": ["solved", { "kind": "box", "title": "Before you ask", "body": "Search first — most questions have an answer.", "tone": "note" }] },
        "announcements": { "map": { "show": "never" }, "replyBar": false, "actions": [] }
      }
    }
  }
}

A discussions-style thread that is not any replica: the title after the author inside the opening post with a solid Solved pill, the meta line as a sentence, the topic's number after the title, stacked map stats, "Sep 20" timeline ends, an "Ask" button kept beside the title on a phone, and two grouped boxes in the rail:

{
  "forum": {
    "post": { "avatar": { "position": "inline", "size": "sm" }, "name": "username-plain" },
    "topic": {
      "titlePlacement": "in-first-post-after-author",
      "header": {
        "solvedPill": { "placement": "before-title", "style": "solid" },
        "metaStyle": "sentence",
        "number": true,
        "cta": { "label": "Ask", "href": "new", "phone": "inline" }
      },
      "map": { "show": "always", "style": "stacked" },
      "timeline": { "placement": "beside-column", "phone": "pill", "dateStyle": "day-month" },
      "rail": [
        "meta",
        { "kind": "box", "title": "Guidelines", "body": "Be kind.", "tone": "note" },
        { "kind": "box", "title": "Office hours", "body": "Thursdays.", "tone": "note", "group": true }
      ]
    }
  }
}

A quiet reading page: the title inside the first post, short timeline dates, no Translate even with a provider, Watch as a link, and nothing at the foot for signed-out readers:

{
  "forum": {
    "topic": {
      "titlePlacement": "in-first-post",
      "titleSize": { "sm": "22px", "lg": "34px" },
      "align": "start",
      "translate": "never",
      "watch": { "style": "link" },
      "acceptedBanner": false,
      "timeline": { "placement": "beside-column", "dateStyle": "short" },
      "composer": { "signedOut": "none" },
      "header": { "stateChip": false, "statusGlyph": true }
    }
  }
}

With post.style: "card" and topic.threading: "nested", a reply's replies are drawn inside its card under an "N replies" disclosure (open when there are three or fewer), nesting at most three deep.

Quotes: the Quote button writes > text then > — [Name](/f/…#post-N). A quote's last line may also be {: cite="Name" cite-href="…"}. Either links the cite back to the post and notifies the quoted author. [grid] … [/grid] around images is read as a gallery.

typography.levels.topicTitle sizes topic titles ([data-fx-topic-title]) apart from every other page title. The theme component outlet forum-post-author-after sits after the author name in every post header.

Account, search and composer

Forum-only behaviour lives under theme.forum.chrome. Header keys that apply on every surface (signInStyle, ctas[].action, sidebarToggle, header.accentTop, subnav.*Dark, subnav.active) are chrome.* keys: see "The reader's account control, and the forum header keys" in the theming guide.

Key

What it does

chrome.composer.mode

inline (default) keeps the reply box at the end of the thread. dock makes it a bar stuck to the bottom of the topic column: "Reply" expands it (up to 60% of the screen), Escape folds it. Drafts autosave either way

chrome.searchPage.filters

Where the /search page's filter panel sits: side (default), top, none (the box alone; operators typed into it still work), or disclosure — the top row folded in a closed "Advanced filters" disclosure above the results (open when a filter is already applied; its closed size is fixed, so opening it never moves anything above it). Theme scope; no layer default. Example: "searchPage": { "filters": "disclosure" }

chrome.searchPage.avatar

A result's starter face: above (default) over the title, or start — a 40px face beside a column of title, meta and excerpt. Theme scope; no layer default. Example: "searchPage": { "avatar": "start" }

chrome.searchPage.head

The results' heading: line (default) — the muted "12 results" line; count — a heading, "12 results for “query”", with a Sort picker (relevance, latest post, most votes, most views, newest topic — the order filter) and a Type picker (topics and posts, or titles only — the in:title filter) at its end. The pickers are links, so they work with no script. Theme scope; no layer default. Example: "searchPage": { "head": "count" }

{
  "forum": {
    "chrome": {
      "composer": { "mode": "dock" },
      "searchPage": { "filters": "top" }
    }
  }
}

Mix and match: filters and avatar are independent of each other and of every look. A combination that is not any replica — the filters folded away and faces beside each result, beside a docked composer:

{
  "forum": {
    "chrome": {
      "composer": { "mode": "dock" },
      "searchPage": { "filters": "disclosure", "avatar": "start" }
    }
  }
}

What a signed-in reader gets with no key set:

  • An account menu in the header in place of their name: My activity, Profile, Bookmarks, Notifications, Preferences, Account & privacy, Sign out — the same menu on every surface of your organization, configurable with chrome.accountMenu (entries, order, your own links, the name, the bell). There is no "Drafts" entry: a reader's drafts are kept per composer in their browser, not on the server.

  • /me — "My activity": everything the reader posted, replied, reacted to, bookmarked and watched across your organization, with edit, delete, unreact, unbookmark and unwatch (one at a time or in bulk), and their account and privacy controls. See "Who sees what" in the theming guide, which also covers audience on every header, sidebar (forum.sidebar.custom[]) and front-page item — "Start free" for signed-out readers, "My activity" for members.

  • A bell beside it, with a dot for unread notifications. It checks once a minute while the tab is visible. "See all" opens /notifications.

  • /preferences: watch, track or mute the forum, its categories and its tags; email delivery and per-type settings; name, bio, location, website, cover image and status.

  • /search: the full search page with filters (see the forum guide).

The reader composer's toolbar has heading, bold, italic, strike, code, quote, list, link, table, a collapsible :::details block and an attachment button (images and PDF). The poll builder offers choice, ranked and number polls and "Show who voted". Profiles and user cards show the member's cover image, status and custom fields (a field marked "show on card" also appears on the card, up to three). A group flair pill draws the group's flairIcon before its label — a glyph name or an https: picture (see "Seeding a forum with real authors" in the forum guide).

profile — a member's page (/u/<username>)

Key

Shape

Default

What it does

profile.avatarSize

integer px, 45–160

56

The avatar beside the member's name. Discourse draws 120

profile.summary.sections

array (≤9) of stats, topReplies, topTopics, topLinks, mostRepliedTo, mostLikedBy, mostLiked, topCategories, topBadges

unset

The Summary tab's sections, in this order. Consecutive list sections (topReplies … mostLiked) sit side by side in two columns; stats, topCategories (a table: category, topics, replies) and topBadges take the full width. Unset draws today's tab (tiles, most active topics, most liked posts, category and tag chips, badges); [] draws nothing

profile.rail

array (≤2) of solved, tags

unset

A right-hand column at ≥1280px: the topics this member solved in this forum, and the forum's trending tags (last 30 days, else all-time). Not drawn below 1280px. Unset = no rail

profile.statsStyle

tiles | inline

tiles

The Summary's six numbers as the tile grid, or as one line — "12 Topics created · 40 Posts created · …". The same numbers either way

profile.itemMeta

array (1–3) of date, likes, replies

unset

The facts under each top-topic and top-reply row, in this order, joined by " · " — ["date","likes"] draws "Mar 4, 2026 · ♥ 12". replies applies to topics only; a fact a row has no value for is skipped. Unset: "3 replies · 5 votes" on topics, "5 likes" on replies

profile.tabs

array (1–3) of summary, activity, badges

["summary","activity"]

The tabs, in this order; the first one opens. badges lists every badge the member holds (icon, what it is for, when it was earned) and is drawn only for a member who holds one

{
  "forum": {
    "profile": {
      "avatarSize": 120,
      "summary": {
        "sections": ["stats", "topReplies", "topTopics", "topLinks", "mostRepliedTo",
                     "mostLikedBy", "mostLiked", "topCategories", "topBadges"]
      },
      "rail": ["solved", "tags"]
    }
  }
}

One line of numbers, dated rows and a Badges tab (all three scope: theme — the whole forum's profiles):

{
  "forum": {
    "profile": {
      "statsStyle": "inline",
      "itemMeta": ["date", "likes"],
      "tabs": ["summary", "activity", "badges"]
    }
  }
}

Every list is at most six rows and holds only what the reader may see: topics in a category they cannot open never appear on someone's profile. A section with nothing in it says "Nothing yet." rather than disappearing, so the layout you chose holds. The data is GET …/forum/u/{username}/summary (add ?rail=solved,tags for the rail).

Search results (/search) lead each row with the starter's avatar — their picture, or their initials on their own colour.

Rows, posts, print and your own words

More building blocks. Each is optional, works under every look, and unset keeps what the forum drew before.

Key

Values

Unset

What it does

list.rowLink

first, unread

first

Where a row's title takes a signed-in reader: the first post, or the first post they have not read (Discourse's default) unless the category says to open at the first post. A signed-out reader, and a topic read to the end, always open at the first post. Also per category and per feed block

list.excerptSource

excerpt, gist, gist-first

excerpt

What the excerpt line says: the opening post's first words, the topic's one-line AI gist (nothing until one is written), or the gist where there is one and the excerpt otherwise. Gists exist once the forum's aiGist behaviour key is on and an AI provider is connected

list.paging

pages, more, infinite

pages

pages (and unset) now draws real page links under the list: the page numbers up to the current one and Next, plus the last page when the filter row shows the topic count. Before, nothing was drawn, so a forum showed its first 30 topics and no way past them. Page 1 is the bare address. Links work with no script

post.staffAccentDark

hex

staffAccent in both schemes

The staff accent on the dark page

topic.print

thread, op

thread

What a printed topic carries: every post, or the opening post (and an accepted answer) only. The reply bar, filter row, action rows, timeline and composer never print

sidebar.communityLinks

any of topics, categories, tags, members, leaderboard, badges, about, bookmarks, search, in order, each once

topics, categories, tags, members, leaderboard, badges, about

The sidebar's COMMUNITY section. frontDoor.showLeaderboardLink: false still drops the leaderboard, and now only the leaderboard (it used to take Badges with it)

sidebar.communityLabels

{topics?, categories?, …}, ≤ 40 characters each

the built-in names

Those links in your own words

front.filters.linkLabels

{rss?, members?, leaderboard?, badges?, about?, bookmarks?, markRead?}, ≤ 24 characters each

the built-in names

The filter row's end links in your own words (the tabs already had labels)

front.filters.items

adds assigned, posted, participated

—

A signed-in reader's own lists: topics assigned to them, topics they started, topics they replied in

categoryPage.subcategories

as before

—

A category's own subcategoryListStyle now wins over the theme's choice (the category is the narrower scope)

translations

{ "<locale>": { "labels": { "<your words>": "<translation>" } } }

none

Your own forum words in each reader language. See below

Your words in other languages. Sixteen forum keys hold words you typed: tab and link labels, the topic header's button and meta template, the breadcrumb root, rail and sidebar titles, the category page title. On a forum translated into Spanish those showed your English on every /es/ page. forum.translations maps your original words to their translation, per language, wherever the words appear; reordering the tabs does not scramble it.

{
  "forum": {
    "front": { "filters": { "labels": { "latest": "Active" }, "linkLabels": { "about": "Our rules" } } },
    "translations": {
      "es": { "labels": { "Active": "Activos", "Our rules": "Nuestras normas", "Ask Question": "Preguntar" } },
      "de": { "labels": { "Active": "Aktiv", "Our rules": "Unsere Regeln" } }
    }
  }
}

Per category, more of the topic page. topic.byCategory["<slug>"] now also takes header, share, timeline, threading and post, so an announcements category can read flat with no timeline while Help reads as Q&A comments with gutter votes:

{
  "forum": {
    "topic": {
      "byCategory": {
        "announcements": {
          "threading": "flat",
          "timeline": { "placement": "none" },
          "share": { "mode": "row" },
          "header": { "metaStyle": "sentence", "metaTemplate": "{author} announced this {age}" }
        },
        "help": {
          "threading": "comments",
          "post": { "voteStyle": "gutter", "acceptedStyle": "gutter-check", "staffAccent": "success" }
        }
      }
    }
  }
}

A category override that names its own list anatomy also gets that anatomy's faces (the poster stack with table, the opener with cards or rows), unless it sets avatars itself.

Every forum page is stamped. Tag pages, the member directory, the leaderboard, badges, about, the reader's account pages and the live rooms now carry their page kind, so sidebar.pages, front.panel.scope, layout.topGap and pageHead.placement reach them. Forum pages also follow the theme.mobile scale.

Header and footer. A forum outside a site draws the reader's sign-in in the header (see the defaults below). When it replaces a sign-in link of your own and you set no order, it takes that link's place before the light/dark and language controls. chrome.signInStyle.order chooses explicitly: before-ctas, after-ctas or before-controls. The organization setting general.show_powered_by: false drops the "Powered by Forumulate" credit from the reader footer.

Theme components on forum pages

Theme components draw into these outlets. "Repeats" outlets draw once per row or post; a wrapping outlet's before and after add to the part, replace draws your own.

Outlet

Where

Context

forum-index-above-list, forum-index-below-list

The forum's front page and category pages, above and below the topic list (below the pager)

—

forum-topic-row-after-title (repeats)

After each row's title, in every anatomy

topic.id, topic.slug, topic.title

category-header (wraps)

The heading of a category page: banner, logo and name

category.name, category.slug, category.id, category.color, category.description

forum-categories-above

The /categories page, above the layout

—

forum-sidebar-top, forum-sidebar-bottom

The forum side column (front page and topic pages). The column appears only when something fills it

—

forum-topic-above-title, forum-topic-below-title

A topic page, around the title and its byline

topic.title, topic.slug

forum-topic-below-first-post

Between the opening post (and its polls) and the replies

topic.title, topic.slug

forum-post-after-body (repeats)

After every post's body, the first included

post.number, post.author, topic.title

forum-post-author-after (repeats)

After the author name in every post header

post.number, post.author, topic.title

forum-topic-bottom

After the last post

topic.title, topic.slug

user-card (wraps)

The member card on a member page

user.name, user.username, user.website, user.location

See Theme components.

Where to change it in the studio

Open the forum in the studio. The look picker sits in the front-door panel (Look, which also names an automatic Q&A look). Every other key is in the theme panel's forum group, and clicking a part of the live forum (a row, the filter row, a post header) offers the keys that style it. A category's own fields (approvals, experts, language, list style) are under Categories and tags. A single topic's look is Page look & sharing on the topic. How the forum behaves, rather than how it looks, is in the Forum behaviour sheet; see Community spaces.

Looks and defaults

look — one of clean, board, cards, qa, paper. A look is a set of defaults: every key it sets sits under your own, so any key you write still wins, field by field. Nothing is copied into your theme; change the look and the defaults change with it.

look

What the forum reads like

Suggested arrangement

clean

Neutral white paper and ink in light, near-black in dark, a UI sans; avatars, relative dates, coloured tag chips, a dot category badge, wider columns (72rem list, 760px topic), the topic map at 3+ replies.

(keep yours)

board

clean, plus the classic board: a posters stack and Replies · Views · Activity columns, the category sidebar, underlined filter tabs.

Dense list

cards

clean, plus the OP's avatar, a thumbnail and the excerpt on every card, posts drawn as cards and a one-line topic map.

Categories

qa

clean, plus votes first: a votes / answers / views stat column, the vote count on cards, a vote gutter on posts, replies sorted by votes with a sort control, the accepted answer pinned first, comments under answers.

(keep yours)

paper

The forum as it was before 2026-10: warm paper, Archivo, no avatars, absolute dates, the pill badge.

(keep yours)

PATCH /orgs/{orgId}/spaces/{spaceId}/theme
{ "forum": { "look": "board" } }

The arrangement column is a suggestion only: a look never changes the space's layout preset (PUT …/preset), which stays yours.

What each look sets. clean is the full default layer (the table in the defaults); every other look is a small patch over it, and these are all the keys each one patches:

Look

Keys it sets

board

list.anatomy: "table", list.avatars: "posters", list.columns: ["replies","views","activity"], sidebar.enabled: true, front.filters.style: "underline"

cards

list.anatomy: "cards", list.avatars: "op", list.thumbnails: "start", list.excerpt: "all", post.style: "card", topic.map.style: "strip", front.welcome: "auto"

qa

the Q&A keys below, plus list.anatomy: "cards", list.avatars: "op", list.statusGlyph: "checkbox", post.style: "card", post.byline: "card-end", topic.threading: "comments", topic.header.meta: ["asked","modified","views"], and the front door's showVotes: true

paper

turns the clean layer back off: the forum as it was before 2026-10

The Q&A keys are list.stats (position: "start", fields: ["votes","answers","views"], acceptedStyle: "box", phone: "strip"), post.voteStyle: "gutter", post.acceptedStyle: "gutter-check" and topic replySort: "votes", sortControl: true, answerPlacement: "pinned-first". forum.qa: "on" lays exactly these on top of whatever look you chose ("auto": only when most topics are questions; "off": never, not even the automatic switch).

Because a look is only these keys, you can take one look's part and another's part. Two combinations no look or replica gives:

The board table and sidebar, with Q&A voting only where it matters, rows that open where the reader left off, and the AI gist as the excerpt:

{
  "forum": {
    "look": "board",
    "list": { "rowLink": "unread", "excerpt": "all", "excerptSource": "gist-first" },
    "topic": {
      "byCategory": {
        "help": { "threading": "comments", "post": { "voteStyle": "gutter", "acceptedStyle": "gutter-check" } }
      }
    }
  }
}

The cards list with clean posts (one key overridden), a renamed and reordered COMMUNITY section, and topics that print as the question alone:

{
  "forum": {
    "look": "cards",
    "post": { "style": "rule", "staffAccent": "#0f766e", "staffAccentDark": "#5eead4" },
    "sidebar": {
      "enabled": true,
      "communityLinks": ["topics", "search", "members", "about"],
      "communityLabels": { "topics": "Everything", "about": "House rules" }
    },
    "topic": { "print": "op" }
  }
}

An unset look can be qa (2026-10-12). When you have not chosen a look and at least half of the forum's published topics — five or more of them — are questions (kind: "QUESTION"), the forum reads with qa instead of clean: the votes / answers / views column, the accepted answer first. It is worked out when a page is read (re-counted every few minutes), so it follows the forum as it grows, and a look you set always wins. The studio's front-door panel names the result: Look → "Q&A (automatic)". To keep clean on a question-heavy forum, set it:

{ "forum": { "look": "clean" } }

Where a look reaches. Set on the space, it governs that forum. Set on a site theme, it reaches every forum in the site — the theme keys, the paper and type. The front page's switches (the category badge, the vote count, the identity band, the search band) follow the look set on the forum space itself; the studio's front-door panel shows that one.

What paper keeps and what it does not. paper turns every default above back off, the front page's switches included. It does not undo the bug fixes that shipped with the forum looks, which have no switch: the Activity column shows the last activity, the dense list stacks on phones, counts read "1 reply", the composer and the related/suggested topics sit in the topic column, and the signed-in reader has an account menu and a bell. It does not change your layout preset either.

Front-page switches a look sets (settings.frontDoor, under whatever you stored there — a stored true/false always wins):

Key

clean, board, cards

qa

paper

showCategoryBadge

auto: on when the forum has two or more categories

auto

off

badgeStyle

dot

dot

pill

showVotes

off (the card's right column shows the reply count)

on

on

showIdentity

off ("New topic" moves into the filter row)

off

on

showSearch

off when the header has its own search box, otherwise on

as left

on

The defaults

Since 2026-10-12 a forum whose owner left these keys unset reads with the clean look — existing forums included. Nothing was written to any space: the defaults are resolved when a page is read, under every key you (or your site) set, so a key you write still wins field by field. {"forum":{"look":"paper"}} turns every row below back off.

Key

Default

paper

neutral paper + UI sans (look)

white / near-black papers, --reader-ui-font

warm paper, Archivo

frontDoor.showCategoryBadge

auto — on with two or more categories

off

frontDoor.badgeStyle

dot

pill

frontDoor.showVotes

off

on

frontDoor.showIdentity / showSearch

off / off under a header search

on / on

forum.front.filters.newTopic

on ("New topic" in the filter row)

off

forum.tagStyle, forum.list.tags

chip, chips (tag colours)

muted text

forum.list.anatomy

table (Replies · Views · Activity) on the default Categories arrangement and under board; cards under cards and qa

the preset's (cards)

forum.list.avatars

posters with the table, op with cards or rows

none

forum.list.excerpt

pinned

none

forum.list.stateGlyphs, .pin, .header.case

on, glyph, title

as before

forum.dates.style

compact ("3h", "5d")

absolute

forum.sidebar.enabled

on for the Categories and Dense list arrangements

off

forum.categoriesPage

{ layout: "list", beside: "latest" }

today's stack

forum.categoryPage.liftDark

on (category colours lifted for dark)

off

home category rail (front.categoriesLayout)

ruled rows; tiles under cards, and under clean when three or more top-level categories each have a logo or image icon (tiles sit before the topics, with the welcome band at four or more)

grid

forum.post.avatar

{ position: "gutter", size: "md", shape: "circle", positionSm: "inline" }

none

forum.post.date, .bodySize, .headingScale

relative, base (16px), body

as before (14px)

forum.post.opChip, .staffAccent

on, brand

none

forum.post.actions, .menu, .menuHidden

icons at the end; like, link, reply; the rest under "…"

today's text row

forum.post.gapMarkers

7 ("N days later")

none

forum.topic.measure, layout.widths.list

760 px, 72rem — not when contentWidth is standard or full

42rem, 48rem

forum.topic.map

{ show: "auto", minReplies: 3, style: "full", fullFrom: 10 } — the full box from 10 replies, a strip below

none

forum.topic.answerPlacement

excerpt (posts in order, the answer quoted under the question)

pinned-first

forum.topic.share

{ mode: "icon", align: "end" }

the full row

forum.topic.actions

["share"] — no "Translate · Bookmark" row under the topic

the full row

forum.topic.replyBar

{ signedOut: "bar", placement: ["end"] } — "Leave a reply…" for a signed-out reader

none

forum.list.titleLines

{ sm: 3, md: 2, lg: 2 } — a title wraps instead of one line and an ellipsis

1

forum.list.kindChip

off — no "question" chip on rows

on

forum.list.stats.phone

strip — the votes / answers / views strip above the title on a phone

hide

forum.front.categories

auto — topics first; no second list where the forum sidebar already lists the categories, else the categories beside the topics from 1024px (aside)

before

forum.sidebar.subcategories

nested — the top level, each parent opening to its children

flat

forum.front.welcome (cards look)

auto — the welcome band first with four or more categories

hide

forum.post.reactionsRow

inline — the add-reaction button inside the action row

separate

forum.post.badges

hover — badge glyphs after a name on hover or focus only

always

forum.post.quote.avatar

on — the quoted author's face in a quote card

off

forum.look (unset)

qa when ≥ 50% of ≥ 5 published topics are questions, else clean

—

logoDarkMode

invert for a lone logoUrl or logoMark (not with a dark/light pair, nor when pinned dark)

none

forum.topic.timeline

{ placement: "beside-column", phone: "pill" }

fixed at the window's edge

forum.topic.breadcrumb, .header

trail; no kind chip, a status glyph. The trail ends in the category, so the meta line's category chip is not drawn while header.category is unset (set chip or text to have both)

"All topics"; the kind chip

layout.indexHeader

hide — the forum's name stays as a visually hidden <h1>

shown

chrome.themeToggle.style

cycle (a forum outside a site)

mono

chrome.signInStyle

{ look: "outline" } (a forum outside a site)

a text link

chrome.header.brandSize

{ sm: 16, md: 18 } — the name at a title's size (a forum outside a site)

the header row's 14px

header sign-in

always drawn on a forum that accepts reader accounts, in the server HTML (it opens the platform sign-in until the forum's own providers are known)

the same — not a look key

Inside a site the site's header governs, so the two chrome rows apply only to a forum on its own. A site whose blog should match its forums' warm paper can set {"forum":{"look":"paper"}} on the site theme, which reaches every forum in it. The layout preset is unchanged: a forum that never chose one is still Categories.

The Forumulate teamUpdated