Hook reference
Every data-region, data-component, data-variant and data-part handle Quiet ships, in lookup tables: header, homepage, feeds, posts and their table of contents, podcast, videos, portfolio and footer.
Every handle Quiet ships, grouped by where it appears. Compose them from the outside in: region → component[variant] → part.
New to this? Start with the Code injection overview, and take ready-made rules from Common snippets.
Site-wide chrome
| Hook | What it is |
|---|---|
[data-region="site-header"] | The whole header bar |
[data-region="masthead"] | The logo area at the header's start |
[data-region="primary-nav"] | The desktop menu |
[data-region="mobile-menu"] | The drawer that opens on a phone |
[data-region="main"] | Everything between header and footer |
[data-region="site-footer"] | The whole footer |
[data-region="footer-nav"] | A footer link column |
[data-region="footer-legal"] | The small print row at the very bottom |
[data-component="footer-brand"] | The logo block in the footer |
[data-component="theme-toggle"] | The light and dark button |
[data-component="social-links"] | A social icon row |
[data-component="legal-links"] | The links inside the small print row |
[data-part="logo"] | The logo image, or your site title when there is no logo |
[data-part="wordmark"] | The link wrapping the logo |
[data-part="icon"] | A menu item's icon, when it has one: an uploaded image, or a glyph added from Code injection. Present on every surface the item appears on |
[data-part="label"] | A menu item's text. An icon-only item has none |
[data-social-platform="…"] | One social icon: instagram, twitter, facebook, linkedin, bluesky, threads, mastodon, tiktok, youtube, rss, and website on a team card |
The logo appears twice, in the header and the footer, so scope by region to size them separately: [data-region="masthead"] [data-part="logo"].
Menu icons appear in the header, the drawer, the footer and on the 404 page, so scope by region when you mean one of them. This sizes only the header bar's icons:
<style>
[data-region="primary-nav"] [data-part="icon"] { width: 20px; height: 20px; }
</style>Do not restyle the menu row itself
Quiet measures the header menu to decide what fits, and rebuilds the drawer as a set of collapsible groups. Rules that change the width, overflow, display or position of the menu list will fight that. Style the links inside it, or the region around it.
Feeds and cards
| Hook | What it is |
|---|---|
[data-region="post-feed"] | Any list of posts: the archive, a tag page, an author page, the video channel |
[data-component="post-card"] | One post in a list |
…[data-variant="grid"] | The card with a picture, used in Read next and in the Latest writing card row |
…[data-variant="numbered"] | One row of the Latest writing list |
…[data-variant="row"] | One line of the archive, a tag page or an author page |
…[data-variant="row"][data-featured="true"] | The line of a post you marked Featured. An unstarred line has no data-featured, so [data-variant="row"]:not([data-featured]) selects only those |
[data-component="featured-strip"] | The row of picture cards at the top of the archive's first page. Its cards are [data-component="post-card"][data-variant="grid"], so scope card rules to the strip: [data-component="featured-strip"] [data-part="title"]. The list inside it is a sideways rail on a phone: leave its display, overflow, width and margin alone, as with the topic tiles |
[data-component="month-group"] | One month's section, on all three archives |
[data-part="month"] | The month heading |
[data-component="pagination"] | The newer and older links |
[data-region="archive-header"] | The header on the archive, a tag page or an author page |
…[data-variant="tag"], …[data-variant="author"] | Which of the three it is; the archive's own header has no variant |
Gone in 1.2.0
The tag page's old two-column card, [data-variant="list"], no longer exists: tag pages use the same month-by-month lines as the archive. A rule written for it should target [data-variant="row"] instead, scoped to tag pages with Ghost's body class: .tag-template [data-variant="row"].
Parts inside a card
| Hook | What it is |
|---|---|
[data-part="kicker"] | The category label above the title |
[data-part="title"] | The title |
[data-part="excerpt"] | The summary line |
[data-part="meta"] | The line holding date and reading time |
[data-part="date"] | The date |
[data-part="read-time"] | The reading time |
[data-part="image"] | The picture |
[data-part="badge"] | The Members or Paid pill |
[data-part="flag"] | The small star before the title of a featured archive line |
The homepage
| Hook | What it is |
|---|---|
[data-region="home-section"] | Any homepage band |
…[data-variant="intro"] | The introduction |
…[data-variant="hub"] | Topic tiles |
…[data-variant="latest"] | Latest writing |
…[data-variant="logos"] | The logo strip |
…[data-variant="testimonials"] | Testimonials |
…[data-variant="podcast"] | The podcast band |
…[data-variant="videos"] | The video band |
…[data-variant="projects"] | The portfolio band |
…[data-variant="gallery"] | The photo strip |
[data-component="hub-tile"] | One topic tile |
[data-component="hub-link"] | One link in the More topics row |
[data-component="newsletter-panel"][data-variant="band"] | The homepage signup band |
[data-component="episode-row"][data-variant="card"] | An episode in the podcast band |
[data-component="video-card"][data-variant="grid"] | A film card, on the band and on the channel |
[data-component="project-card"][data-variant="ledger"] | A project row, on the band and on the portfolio |
[data-part="body"] | The short note beside a band's heading |
[data-part="index"] | The number on an episode row, a project row, or a row of the Latest writing list |
[data-component="post-card"][data-variant="numbered"] | One row of the Latest writing list |
The introduction carries three extra handles for its layout:
| Hook | Values |
|---|---|
[data-layout="…"] | split-left, split-right, panel-left, panel-right. The centred default has no data-layout, so [data-variant="intro"]:not([data-layout]) selects it |
[data-ratio="…"] | square, 3x4, 2x3, 9x16. The 4:5 default has none |
[data-corners="…"] | sharp, round. The default has none |
And the logo strip carries two:
| Hook | Meaning |
|---|---|
[data-treatment="mono"] | The page is tagged #logos-mono |
[data-motion="static"] | The page is tagged #logos-static |
Latest writing and Testimonials carry their layout and feed the same way:
| Hook | Meaning |
|---|---|
[data-variant="latest"][data-layout="cards"] | The page is tagged #latest-cards. The numbered list has no data-layout, so [data-variant="latest"]:not([data-layout]) selects it |
[data-variant="latest"][data-source="featured"] | The page is tagged #latest-featured. A band of the newest posts has no data-source |
[data-variant="testimonials"][data-layout="marquee"] | The page is tagged #testimonials-marquee. The grid has no data-layout |
[data-variant="testimonials"][data-motion="static"] | The page is tagged #testimonials-static |
The three strips that can move (logos, photographs, quotes) also carry state you may select on but should never set: [data-fits] when everything fits on one line and nothing moves, [data-paused] while a reader has pressed pause, and [data-focus-hold] while a keyboard user's focus is inside a moving row.
Three containers to leave alone
The photo strip's scrolling rail, the quote marquee's rail, the topic tiles' list and the archive's Featured strip list are all measured by a script, so do not set display, overflow, width or margin on them. Style the band around them ([data-variant="gallery"], [data-variant="testimonials"][data-layout="marquee"], [data-variant="hub"], [data-component="featured-strip"]) or the items inside. The tiles and the Featured cards become a sideways row below 640 pixels.
Posts and pages
| Hook | What it is |
|---|---|
[data-region="post"], [data-region="page"] | The whole article |
[data-region="post-header"], [data-region="page-header"] | The title block |
…[data-variant="split-left"], …[data-variant="split-right"] | The title block in one of the two split layouts that put the picture beside it (Post and page layouts). The default centred block has no data-variant, so [data-region="post-header"]:not([data-variant]) selects it |
[data-region="post-meta"] | The byline |
[data-region="feature-image"] | The picture at the top, in every layout |
…[data-variant="full"], …[data-variant="inline"] | That picture when a post is tagged #hero-full or #hero-inline. The default width has no data-variant |
[data-part="image"] | The picture itself |
[data-region="post-media"] | The player area on a film, in place of the feature image |
[data-region="post-body"] | The article itself. Ghost's own kg- classes work inside it |
…[data-variant="dropcap"] | The article when it is tagged #dropcap; the enlarged letter is its first paragraph's ::first-letter |
[data-region="related"] | The Read next row |
[data-region="post-nav"] | Previous and next |
[data-component="post-nav-link"] | One of those links |
…[data-variant="prev"], …[data-variant="next"] | Which one |
[data-component="author-card"] | The Written by card. On the team page the same name marks each person, with a data-variant; the Written by card has none, so [data-component="author-card"]:not([data-variant]) selects it alone |
[data-component="social-share"] | The share row |
[data-share-platform="…"] | One share control: bluesky, linkedin, x, facebook, threads, email, link |
[data-region="comments"] | Ghost's comments |
[data-part="author"], [data-part="date"], [data-part="read-time"] | Pieces of the byline |
[data-part="eyebrow"], [data-part="lede"] | The label above a heading, and the line under it |
The table of contents
On every post and page with three or more sections (Table of contents).
| Hook | What it is |
|---|---|
[data-component="table-of-contents"] | Every part of it a reader sees. Hiding this one name turns the table of contents off across the site |
…[data-variant="inline"] | The Contents line above the article, with its list |
…[data-variant="button"] | The Contents button in the corner |
…[data-variant="sheet"] | The list the button opens, inside the panel or phone sheet |
[data-component="heading-link"] | A section link, inside [data-region="post-body"]. It marks both halves: the link button at the end of a listed heading (h2 [data-component="heading-link"]), and the keyboard link just before the heading, which draws nothing of its own |
[data-part="title"] | The word Contents on the Contents line |
To style the entries, scope by variant: [data-variant="sheet"] a[aria-current="location"] is the section the reader is in, inside the list the button opens.
Leave the panel itself alone
The sheet handle sits on the list inside the panel, not on the panel. Hiding the panel while it is open would leave the page behind it unusable, with nothing to close. Hide the button to stop the panel ever opening.
A rule that picks a heading by what comes before it
A section link sits just before its heading in the article. A rule like .gh-content p + h2 therefore no longer matches a listed heading on a post with a table of contents, because the element before it is now the link. .gh-content > h2 and h2 + p still match as before. To keep such a rule working, add the link to the chain as a second selector: .gh-content p + h2, .gh-content p + [data-component="heading-link"] + h2.
Membership
| Hook | What it is |
|---|---|
[data-component="member-cta"] | The paywall card |
…[data-variant="signup"] | Shown to a signed-out reader |
…[data-variant="upgrade"] | Shown to a member whose tier does not cover the post |
…[data-variant="account"] | The account link in the header |
[data-component="newsletter-panel"] | A signup band |
[data-component="newsletter-panel"]:not([data-variant]) | The one at the end of a post or page, rather than the homepage band |
[data-component="subscribe-form"] | The email field and its button |
[data-part="hint"] | The fine print under the field |
[data-part="success"] | The confirmation after a signup |
The podcast
| Hook | What it is |
|---|---|
[data-region="podcast"] | The whole podcast page |
[data-region="podcast-hero"] | Cover art, name and description |
[data-region="podcast-body"] | The page body slot, where a player embed goes |
[data-component="episode-row"] | An episode in the list (the band's version adds [data-variant="card"]) |
[data-component="platform-list"] | The listening links row |
[data-part="cover"] | The cover art |
The video channel
| Hook | What it is |
|---|---|
[data-region="videos"] | The whole channel page |
[data-region="videos-hero"] | Name, tagline and channel links |
[data-region="videos-body"] | The page body slot |
[data-component="video-card"] | A film card |
[data-part="poster"] | The poster image |
[data-part="play"] | The play cue on a poster |
The portfolio
| Hook | What it is |
|---|---|
[data-region="projects"] | The whole portfolio page |
[data-region="projects-hero"] | Its heading block |
[data-region="projects-body"] | Its optional introduction |
[data-region="projects-feed"] | The list of projects |
[data-component="project-card"][data-variant="ledger"] | One project row |
[data-region="project-nav"] | Previous and next on a project page |
[data-component="project-nav-link"] | One of those links, with [data-variant="prev"] or [data-variant="next"] |
[data-part="index"] | The row's number |
The team page
| Hook | What it is |
|---|---|
[data-region="team"] | The whole page |
[data-region="team-hero"] | Its heading block |
[data-region="team-body"] | Its optional introduction |
[data-region="team-feed"] | The list of people |
…[data-layout="compact"] | The list when the page is tagged #team-compact. The portrait grid has no data-layout |
…[data-treatment="mono"] | The list when the page is tagged #team-mono |
[data-component="author-card"][data-variant="portrait"] | One person as a portrait card |
[data-component="author-card"][data-variant="compact"] | One person as a directory row |
[data-component="social-links"][data-variant="author"] | A person's row of account links, here and under the heading of their author page. The footer's row is [data-variant="icons"] |
[data-part="image"] | The picture, or the first-letter stand-in |
[data-part="title"], [data-part="excerpt"] | The name and the bio |
[data-part="meta"], [data-part="badge"] | The location line and the post count inside it |
Other pages
| Hook | What it is |
|---|---|
[data-region="gallery"] | A page using the Gallery template |
[data-region="newsletter"] | A page using the Newsletter template |
[data-region="contact"] | A page using the Contact template |
[data-component="contact-form"] | The contact form |
[data-region="private"] | The door on a password-protected site |
[data-part="private-links"] | The links on that door |
[data-region="error"] | An error page |
…[data-variant="404"], …[data-variant="generic"] | Which kind |
State you can select on
Read these, never change them.
| Hook | Meaning |
|---|---|
.dark on <html> | Dark mode is active |
[data-color-scheme="System|Light|Dark"] on <html> | Which colour scheme setting the site is using |
[aria-expanded="true"] | An open dropdown or drawer |
#mp-player-host[data-mode="docked"] | The video player is in the corner |
[data-copied] | The Copy link button, or a section link, has just been used |
[data-fits] | A strip fits on screen, so it is not animating |
[data-paused] | A strip has been paused by the reader |
[data-region="team-feed"][data-layout="compact"], …[data-treatment="mono"] | Which team page layout and treatment the page's tags chose |
[aria-current="location"] | The section the reader is in, in both table of contents lists |
[data-component="table-of-contents"][data-variant="button"][data-visible] | The Contents button is showing |
[data-component="heading-link"][data-copied] | A section link has just been copied |
:target | The heading a reader has just jumped to |
Reserved names
Anything starting data-mp-, data-nav-, data-quiet-, data-gallery-, data-copy-, data-toc-, data-footer-nav, data-portal, data-members- or data-ghost- is machinery: Quiet's own or Ghost's. Select them if it helps you scope a rule, but never remove or rename them, and do not build a design on their internals. The same goes for the quiet-nav-icon-* meta tags and the data-quiet-nav-icons block you may have pasted yourself: they are read by Quiet, not styled.
These handles are a promise
Every hook on this page is a stable, documented name. Renaming or removing one counts as a breaking change and is called out in the release notes, so a rule you write today keeps working through theme updates.
Custom fonts
Pick any font from Bunny Fonts or Google Fonts, copy the code the page builds for you, and paste it into Ghost. Nothing to edit.
Common snippets
Copy-paste CSS for Quiet's most-requested tweaks: logo size, menu icons, reading width, hiding parts of a post, restyling cards, colour changes and dark-mode-only rules.