The block catalog

Every object this app is built from, rendered once, with the name we call it. Order a block by name — “a contact row with a checkbox”, “an entry row with Restore and Purge” — and that is a complete instruction; nobody has to remember a class.

The stylesheet on this page is the app's own, copied verbatim, so a specimen here cannot drift from what ships. Rebuild with python scripts/build_catalog.py after any UI change. Two rules run underneath everything below: every block is standard unless it is named special, and a right-pane page is 600 wide unless it says otherwise out loud.

0The index

Every name on this page, and the class behind it.

NameClassSection
the paneGeometry
the column600px, centredGeometry
chromeGeometry
sub-page header.drw-headThe page
group.drw-groupThe page
group title.drw-gtitleThe page
page-wide button.drw-wideThe page
the foot.modal-footThe page
chat row.chat-itemthe chats list · Forward · Shared chats
contact row.drw-item.ppl-rowThe rows
drawer row.drw-item + .drw-chevThe rows
toggle row.drw-item.priv-row + .priv-swThe rows
dropdown row.drw-item + .ddThe rows
text row.drw-item.priv-row + .priv-boxChange password · Invite code
entry row.drw-item.storerowArchived chats · Deleted chats · Deleted Seen · Deleted persona
search row.srch-field / .modal-searchInvite people · Invite persona · the search page · the persona browser
pill row.pb-sortInvite persona · the persona browser
a frozen row group.pick-tools / .pick-rowInvite persona (search + pills) · Invite people (search only)
commit button.drw-commit / .dialog-commitButtons
primary / ghost.btn-primary / .btn-ghostButtons
small button.np-prevButtons
pill.lib-mineButtons
icon button.iconbtnthe chat top bar: the wildcard, and back-to-chats on a phone
the head pair.head-icothe head of the Chats · Friends · Discover lists
bubble.msg / .bubbleIn the chat
avatar.fr-av / .avatarIn the chat
toast.toast / .toast.showany act with an undo — archive, delete, mute
badge.drw-cntFeedback
empty state.store-emptyFeedback

1Geometry

Where a thing sits, before it is anything else. These are diagrams: a fixed full-screen page cannot be rendered inside a box.

the pane

On a wide window there are two: the list on the left (363), the page on the right. On a phone the right pane IS the screen.

phone · 390
left pane
363
right pane
the column · 600
wide · the column · 600
left pane
363
right pane
the column · 600
the column600px, centred

Anything you read or fill in is 600 wide and centred. THE DEFAULT — a page opts out, never in. ⚠ Cap the CONTENT, never the scroller: the scroller stays the pane so its bar sits at the far right. Special by name: the chat (790) and the Vibes feed.

phone · 390
scroller = the pane
content · 600
wide · the column · 600
scroller = the pane
content · 600
chrome

A header, a footer, a boundary line: chrome spans the PANE, so it is the one thing that is not capped. A hairline under a header is the line between what freezes and what scrolls.

phone · 390
header — spans the pane
600
wide · the column · 600
header — spans the pane
600

2The page

A sub-page is a header, a body that scrolls, and sometimes a foot.

sub-page header.drw-head

Back ‹ · title · commit. The ‹ sits 2 from the pane's edge, the commit 10 from the other. One tap on ‹ leaves, from any depth.

phone · 390
Invite people
wide · the column · 600
Invite people
group.drw-group

The band a run of rows sits on: a surface with a soft edge top and bottom. Rows inside are parted by ONE hairline. The break below a group is 10.

phone · 390
First row
Second row
wide · the column · 600
First row
Second row
group title.drw-gtitle

Sits on the PAGE, not on the band, and lines up with the rows' icon column. A titled group drops its top edge — the title is already the separation.

phone · 390
My contents
wide · the column · 600
My contents
page-wide button.drw-wide

A control on the PAGE, not a row of the list — so it keeps the page's own side margin rather than running edge to edge. Sits 22 below what it follows. Log out is the one everyone knows.

phone · 390
wide · the column · 600
the foot.modal-foot

Pinned under the scroller; holds what you have picked. Even air, 10 and 10, plus the home-indicator inset below that.

phone · 390
Abraham LincolnAda Lovelace
wide · the column · 600
Abraham LincolnAda Lovelace

3The rows

The owner's own menu — NINE names since v820, when the search field and the pill row were named rows in their own right. Most of them are ONE class, .drw-item, the workhorse, wearing a different lead and a different trail. Order a row by NAME and it is a complete instruction: “a contact row with a checkbox” needs no drawing.

chat row.chat-item

A chat in a list. The room's TILE leads it; title · time on the first line, who is in it on the second, in their seat colours. Always order the component (roomRowEl) — never re-draw it. ⚠ It bleeds ±16 by design (v711), cancelling the list's own inset, so its stage has to give that back.

lives in the chats list · Forward · Shared chats

phone · 390
wide · the column · 600
contact row.drw-item.ppl-row

A person. 40px round avatar as the lead, the name at 15. One line — the handle is not printed underneath.

phone · 390
wide · the column · 600
drawer row.drw-item + .drw-chev

A door: press it and you go somewhere. Icon lead, label, a chevron in the trail. The sub-line under the label says what it is for.

phone · 390
wide · the column · 600
toggle row.drw-item.priv-row + .priv-sw

A setting you flip in place. The switch is the trail; nothing navigates.

phone · 390
Private historyturning this on can't be undone
Enter to sendon this device
wide · the column · 600
Private historyturning this on can't be undone
Enter to sendon this device
dropdown row.drw-item + .dd

A setting you pick from a list. Same workhorse; the trail is a dropdown showing the current value.

phone · 390
Default language
App and new chats
wide · the column · 600
Default language
App and new chats
text row.drw-item.priv-row + .priv-box

A label over a box you type in. ⚠ It IS the workhorse — since v785 the password and invite fields ride .drw-item.priv-row, which on a sub-page turns its column form on. So it keeps the row's own inset and height, and its label is the row title (house sans 15), not a caption. .acct-field — 11px mono label, no row padding — is the OLD field family and survives only inside Edit profile; showing that here was wrong twice over (owner-caught).

lives in Change password · Invite code

phone · 390
wide · the column · 600
entry row.drw-item.storerow

A stored thing with its own buttons in the trail. Restore · Purge — the store dress.

lives in Archived chats · Deleted chats · Deleted Seen · Deleted persona

phone · 390
Tuesday planning
archived · 3 personas
wide · the column · 600
Tuesday planning
archived · 3 personas
search row.srch-field / .modal-search

A ROW whose whole content is the field — 36px round pill, magnifier lead, a clear × once you type. It takes the --bg fill when it sits on a group, because a surface field on a surface band is invisible. Its hint text is the shared muted tone, stepped back.

lives in Invite people · Invite persona · the search page · the persona browser

phone · 390
wide · the column · 600
pill row.pb-sort

A ROW whose content is a run of pills — a sort or a filter you pick one of. Same inset, same hairline, same wash as any other row; the pills are the house pill, never a copy of its spec.

lives in Invite persona · the persona browser

phone · 390
wide · the column · 600

4Frozen

NOT an object — a PROPERTY you apply to a row or to a row group. The frozen part stays put; everything below it scrolls. Because the frozen part is just rows, it needs no special dress and no hand-drawn line: the last row's own edge IS the boundary.

a frozen row group.pick-tools / .pick-row

A search row and a pill row in one group, frozen — the shape the Invite persona page uses. The rows beneath scroll under it. On Invite people the same thing is done with a single frozen search row.

lives in Invite persona (search + pills) · Invite people (search only)

phone · 390
↓ everything below this scrolls
wide · the column · 600
↓ everything below this scrolls

5Buttons

Four jobs, four dresses. A bare text link is not one of them — this app has none.

commit button.drw-commit / .dialog-commit

The one act a page exists for, in the header's right corner. Coral when live, grey when there is nothing to commit.

phone · 390
wide · the column · 600
primary / ghost.btn-primary / .btn-ghost

A dialog's pair: the act, and the way out of it.

phone · 390
wide · the column · 600
small button.np-prev

A control inside the content, not the chrome: 1px line on surface, radius 8, 13px muted, 36 tall. The ← is in-content; a ‹ chevron is always chrome.

phone · 390
wide · the column · 600
pill.lib-mine

A filter or a sort. One shared spec — never copied, or it is consistent right up until the spec moves.

phone · 390
wide · the column · 600
icon button.iconbtn

A bordered glyph button in the chat's top bar. ⚠ NOT retired, though it looks it — audited live because the owner asked: on a phone TWO of them paint (back-to-chats 31×29, the wildcard 39×35); on a wide window just the wildcard, 39×35. Two more exist and are normally invisible — the admin's back-to-console (peek mode only) and the model tag (hidden by a role flag). The magnifier and the ⊕ the owner had in mind are different objects.

lives in the chat top bar: the wildcard, and back-to-chats on a phone

phone · 390
wide · the column · 600
the head pair.head-ico

The magnifier and the ⊕ — ONE button each, the same two glyphs on Chats, Friends and Discover, so they can be aimed at without reading; only the action behind them changes with the root (v704, WeChat's header). A different object from the icon button above: no border, bigger glyph, and it lives in the LIST's head rather than the chat's top bar.

lives in the head of the Chats · Friends · Discover lists

phone · 390
wide · the column · 600

6In the chat

The room's own objects.

bubble.msg / .bubble

A persona speaks in its SEAT colour — a per-room colour, not a per-persona one. The human's own bubble is the coral one.

phone · 390
OOdysseus
The wind is not the problem. The men are.
wide · the column · 600
OOdysseus
The wind is not the problem. The men are.
avatar.fr-av / .avatar

One scale everywhere: 24 inline · 30 row · 40 card · 64 profile · 84 hero. A photo when there is one, the monogram when there is not.

phone · 390
ABCD
wide · the column · 600
ABCD

7Feedback

What the app says back.

toast.toast / .toast.show

A warm taupe pill, never a black slab. It says what happened and, when there is one, offers the undo. ⚠ It is visibility:hidden until it carries .show — a specimen without that class renders nothing at all, whatever else you override (which is exactly what happened here until the owner said so).

lives in any act with an undo — archive, delete, mute

phone · 390
Chat archived
wide · the column · 600
Chat archived
badge.drw-cnt

A count in a row's trail.

phone · 390
wide · the column · 600
empty state.store-empty

A sentence where the rows would be — never an empty card behind it.

phone · 390
Nothing deleted.
wide · the column · 600
Nothing deleted.