Your journal
Customize your journal with CSS
Custom CSS lets you change parts of your journal that the Appearance settings do not cover. Start with a theme, then override only the variables or elements you want to change. The editor includes a live preview and shows the selected theme's stylesheet for reference.
How custom CSS is applied
The journal loads its generated color palette first, the selected theme next, and your custom CSS last. Rules in your stylesheet therefore override matching theme rules with the same specificity. You do not need to copy the full theme to make a small change.
When the custom CSS field is empty, the editor shows the selected theme's stylesheet for reference. That text is not stored as custom CSS. Adding rules below it stores only your rules. Editing the theme text itself stores the edited full stylesheet as your custom CSS.
The fine-tuning controls on the Appearance page write CSS variables into a marked block in this field. That block can be saved on every plan. Other hand-written CSS requires Moments Plus access.
The preview updates as you type. The published journal does not change until you select Save changes. Stored custom CSS is limited to 20,000 characters.
Anyone can preview a Moments Plus theme or hand-written CSS. Saving a Moments Plus theme or hand-written CSS requires Moments Plus access.
Common changes
Set shared variables on :root to change the whole journal. The examples below change only one part of the selected theme.
@import url("https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap");
:root {
--journal-heading-font: "Instrument Serif", serif;
/* --journal-body-font does the same for everything you write. */
}:root {
--journal-space-section: 9rem;
}:root {
--primary: #7c4dbd;
--accent: #7c4dbd;
}Journal page structure
Every public journal view has one main.journal-main element. It also has a class for the current view: .journal-view-home, .journal-view-moment, .journal-view-page, or .journal-view-collection. The same header component appears inside each view, and the footer follows the main element.
A moment uses article.journal-moment both on the journal home and on its own page. A rule that targets .journal-moment affects both. Prefix the selector with .journal-feed when a rule should apply only to moment cards in a list.
A written page uses article.journal-page and div.journal-page-body. A collection uses .journal-main-collection and h1.journal-collection-title. Moment collections use the standard feed hooks; photo collections use the standard .journal-photos and .journal-photo hooks.
<main class="journal-main journal-view-moment">
<section class="journal-hero">
<img class="journal-hero-image"> <!-- only with a hero photograph -->
<div class="journal-hero-scrim"> <!-- darkens it so the text reads -->
<div class="journal-hero-glow"> <!-- the theme's soft accent light -->
<div class="journal-hero-inner"> <!-- the column a stylesheet centres -->
<div class="journal-avatar"><img></div>
<p class="journal-name"><a>Journal name</a></p> <!-- an h1 on the front -->
<div class="journal-bio">…</div>
<nav class="journal-nav"><a>About</a> <a>Contact</a></nav>
</div>
</section>
<article class="journal-main-moment journal-moment" id="moment-photos"
data-photos="4" data-tags="film canal-walk" data-month="06"
style="--moment-photos: 4">
<header class="journal-moment-header">
<div class="journal-moment-meta">
<h1 class="journal-moment-title">Along the canal</h1>
<p class="journal-moment-count">4 photos</p>
</div>
<div class="journal-moment-tags">
<span class="journal-moment-place">London</span>
<span class="journal-moment-date">June 12, 2026</span>
<span class="journal-tag">film</span>
</div>
</header>
<p class="journal-access-note">…</p> <!-- unlisted and protected only -->
<section class="journal-photos-section">
<!-- one photo: .journal-photos-single · two: .journal-photos-pair
three or more: a masonry of columns, or one flat grid when the
theme sets --journal-photo-arrangement: flat -->
<div class="journal-photos">
<a class="journal-photo" data-orientation="portrait"
style="--tile-aspect: 2 / 3"><img></a>
…
</div>
</section>
<section class="journal-note">
<div class="journal-note-text">Your field note</div>
</section>
<section class="journal-conversation">…</section> <!-- replies, mentions -->
</article>
</main>
<footer class="journal-footer">
<div class="journal-footer-credit">…</div>
<div class="journal-footer-links">… <div class="journal-theme-switcher"></div></div>
</footer><main class="journal-main journal-view-home journal-main-home">
<section class="journal-hero">…</section> <!-- identical to the one above -->
<section class="journal-feed">
<div class="journal-feed-list">
<article class="journal-moment" data-photos="4"
data-tags="film canal-walk" data-month="06"
style="--moment-photos: 4">
<header class="journal-moment-header">
<div class="journal-moment-meta">
<a><h2 class="journal-moment-title">Along the canal</h2></a>
<p class="journal-moment-count">4 photos</p>
</div>
<div class="journal-moment-tags">…</div> <!-- place, date, tags -->
</header>
<div class="journal-photos">…</div> <!-- the first six photographs -->
<div class="journal-note">
<div class="journal-note-text">…</div> <!-- clamped to three lines -->
<a class="journal-more">View moment</a>
</div>
</article>
<!-- one article per moment, newest first -->
</div>
</section>
</main>An unlocked password-protected moment is rendered outside the main element. Target .journal-moment directly, rather than relying on a .journal-view-moment ancestor, when a rule must also apply to protected moments.
Change the layout
The header is a flex column, the feed is a list, and each moment's photos are a grid. The following examples show how to change those layouts.
.journal-hero-inner {
text-align: center;
}
.journal-avatar,
.journal-bio {
margin-inline: auto;
}
.journal-nav {
justify-content: center;
}.journal-nav {
order: -1;
margin-top: 0;
margin-bottom: var(--journal-gap-nav);
}@media (min-width: 1024px) {
.journal-hero {
position: fixed;
inset: 0 auto 0 0;
width: min(30vw, 420px);
display: flex;
align-items: center;
border-bottom: 0 !important;
border-right: 1px solid var(--border);
}
main,
.journal-footer {
margin-left: min(30vw, 420px);
}
}.journal-moment .journal-photos {
overflow-x: auto;
align-items: stretch;
scroll-snap-type: x proximity;
}
.journal-moment .journal-photos > * {
flex: 0 0 auto !important;
flex-direction: row !important;
height: clamp(210px, 36vh, 340px);
}
.journal-moment .journal-photos a {
width: auto !important;
height: 100%;
scroll-snap-align: start;
}Add generated labels or numbering
CSS counters and pseudo-elements can add visual labels without changing the stored moment content.
.journal-feed {
counter-reset: chapter;
}
.journal-moment {
counter-increment: chapter;
position: relative;
}
.journal-moment::before {
content: counter(chapter, decimal-leading-zero);
position: absolute;
top: 1rem;
right: 1rem;
font-size: 4rem;
line-height: 1;
color: transparent;
-webkit-text-stroke: 1px var(--border);
}.journal-moment-header {
position: sticky;
top: 0;
z-index: 5;
background: linear-gradient(
to bottom,
var(--background) 65%,
transparent
);
}CSS selectors
The selectors below are the stable hooks used by the built-in themes. They are grouped by page area, with one example for each group.
The header
Contains the journal name, profile photo, bio, and navigation. When a hero image is present, .journal-hero-scrim darkens it and .journal-hero-glow adds the theme's background effect.
- .journal-hero
- .journal-hero-inner
- .journal-name
- .journal-avatar
- .journal-bio
- .journal-nav
- .journal-hero-image
- .journal-hero-scrim
- .journal-hero-glow
.journal-hero {
min-height: 100svh;
display: flex;
align-items: flex-end;
}
.journal-name {
line-height: 0.84;
}
/* The name's size is set on the element, so change the variable
rather than the rule. */
:root {
--journal-title-size: clamp(3rem, 13vw, 11rem);
}Which page you are on
The main element identifies the current view. The .journal-main-* classes identify the primary content container for that view.
- .journal-main
- .journal-view-home
- .journal-view-moment
- .journal-view-page
- .journal-view-collection
- .journal-main-home
- .journal-main-moment
- .journal-main-page
- .journal-main-collection
.journal-view-moment .journal-hero,
.journal-view-page .journal-hero,
.journal-view-collection .journal-hero {
--journal-hero-py: 2rem;
}
.journal-main-moment {
--journal-space-section: 8rem;
}The feed of moments
The journal home and moment collections use a list of .journal-moment articles. Use .journal-feed .journal-moment for list-only rules. A moment collection's title and description appear in .journal-feed-header.
- .journal-feed
- .journal-feed-list
- .journal-feed-header
- .journal-moment
- .journal-feed .journal-moment
@media (min-width: 1024px) {
.journal-feed-list {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: var(--journal-space-section);
}
.journal-moment {
border-top: 0 !important;
}
}What the markup knows
Each moment exposes its tags, photo count, and month as data attributes. Each photo exposes its orientation and natural aspect ratio. Built-in styles do not use these attributes unless a theme adds a rule for them.
- [data-tags~="…"]
- [data-photos="1"]
- [data-month="12"]
- [data-orientation="portrait"]
- --moment-photos
- --tile-aspect
.journal-moment[data-tags~="film"] .journal-photo {
border: 4px solid oklch(0.2 0.02 30);
}
.journal-moment[data-photos="1"] .journal-photos {
max-width: 60rem;
margin-inline: auto;
}
/* In a flat grid, portraits stand two rows tall. */
.journal-photos > [data-orientation="portrait"] {
grid-row: span 2;
}When the reader scrolls
After the reader scrolls past the header threshold, main.journal-main receives data-scrolled. The main element also exposes the measured header height as --journal-hero-height. Set --journal-scrolled-after to move the threshold. Set --journal-masthead: condense to enable the built-in condensed header behavior, then adjust it with the --journal-bar-* variables. Sequence, Contact, and Pixel enable this behavior only above their mobile breakpoints. The html element carries data-journal-view, naming the view on screen: home, moment, page, or collection. Use html[data-journal-view="home"] for a rule that should apply only on the journal front. Do not use html:has(.journal-view-home) for this: the previous view stays in the document, hidden, for back and forward navigation, so that selector keeps matching after a click into a moment or page.
- .journal-main[data-scrolled]
- --journal-scrolled-after
- --journal-hero-height
- --journal-masthead: condense
- --journal-bar-height
- --journal-bar-name-size
- --journal-bar-avatar-size
- --journal-bar-background
- --journal-bar-border
- --journal-bar-inset
@media (min-width: 768px) {
:root {
--journal-masthead: condense;
--journal-bar-height: 3rem;
--journal-bar-name-size: 1rem;
--journal-bar-background: var(--card);
}
}
/* Or style the scroll state directly. */
.journal-main[data-scrolled] .journal-moment-count {
opacity: 0.6;
}A moment's heading
The title and the small line that sits with it. The header is one row, so the pieces can be stacked, swapped or pinned.
- .journal-moment-header
- .journal-moment-meta
- .journal-moment-title
- .journal-moment-count
.journal-moment-meta {
flex-direction: column-reverse;
align-items: flex-start;
gap: 0.5rem;
}
.journal-moment-count {
letter-spacing: 0.24em;
font-size: 0.625rem;
}Where and when
Place, date and tags. They are separate names rather than one line of meta, so any of them can be moved or dropped on its own.
- .journal-moment-tags
- .journal-moment-place
- .journal-moment-date
- .journal-tag
.journal-moment-place {
font-style: italic;
text-transform: none;
letter-spacing: 0;
}
.journal-tag {
background: transparent;
border: 1px solid var(--foreground);
border-radius: 999px;
}The photographs
One photo and a pair have their own layout classes. Three or more use responsive masonry by default. Set --journal-photo-arrangement: flat to use one CSS grid and --journal-photo-columns to set its column count. In a flat grid, apply aspect ratio and other geometry to .journal-photos > * so loading placeholders and loaded photos use the same shape.
- .journal-photos
- .journal-photo
- .journal-photos-section
- .journal-photos-single
- .journal-photos-pair
.journal-photo {
aspect-ratio: 1;
border-radius: 0;
}
.journal-photos {
gap: 2px;
}
/* In a flat grid, crop the placeholders with the photographs. */
.journal-photos > * {
aspect-ratio: 1;
}Field notes
What you wrote under the photographs. In the feed the note is clamped to three lines and ends with .journal-more, the link through to the moment; on the moment's own page it runs in full and the link is not there.
- .journal-note
- .journal-note-text
- .journal-more
.journal-note-text {
max-width: 34rem;
font-size: 1.0625rem;
line-height: 1.75;
}
.journal-note-text p:first-of-type::first-letter {
float: left;
font-size: 3.2em;
line-height: 0.8;
padding-right: 0.08em;
}The conversation
Replies from the open web and mentions from other sites, at the end of a moment's page. The quiet line naming an unlisted or protected moment's reach sits near the top as .journal-access-note.
- .journal-conversation
- .journal-access-note
.journal-conversation {
background: color-mix(in oklch, var(--accent) 6%, transparent);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.25rem 1.5rem 1.5rem;
}Pages, collections, the footer and the switch
Your written pages, a collection's title, the credit line at the bottom, and the control that lets a reader choose light or dark.
- .journal-page
- .journal-page-body
- .journal-collection-title
- .journal-footer
- .journal-footer-credit
- .journal-footer-links
- .journal-theme-switcher
.journal-page-body {
max-width: 46rem;
font-size: 1.0625rem;
}
.journal-footer {
border-top: 0;
opacity: 0.55;
}You only need to override the selectors required for your design. Unchanged parts continue to use the selected theme.
Shared CSS variables
Set shared variables on :root unless the change should apply to one element only. Themes may derive several values from one variable, so changing the base spacing or profile size can update related measurements automatically.
Some values, including journal and moment title sizes, are applied through inline styles that read a variable. Change the variable rather than setting font-size on the element. A few theme rules use !important; prefer the documented variable first and use your own !important only when necessary.
Moments Plus themes also define private variables for their own layouts. You can use them when customizing that theme, but they are not guaranteed to exist in another theme. The current theme stylesheet shown in the editor is the source of truth for those variables.
- --background, --foreground: page background and primary text color
- --card, --muted, --muted-foreground, --border: secondary surfaces, secondary text, and borders
- --primary, --accent: the theme-adjusted accent; --journal-accent is the selected accent before theme adjustments
- --radius, --journal-border-width, --journal-tag-border: shared corner radius and border widths
- --journal-color-scheme: light or dark pins one mode; remove the declaration to return to Reader's choice
- --journal-align, --journal-align-margin: header and content alignment; use left with 0, or center with auto
- --journal-hero-background: plain, glow, wash, dawn, or aurora when no hero image is present
- --journal-hero-py, --journal-section-gap: header padding on wide screens and space after the header
- --journal-heading-font, --journal-body-font: heading and body font stacks
- --journal-heading-weight, --journal-heading-tracking, --journal-body-weight: font weight and heading letter spacing
- --journal-heading-color, --journal-hero-title-color: heading colors in content and in the journal header
- --journal-title-size, --journal-moment-title-size, --journal-collection-title-size: journal, moment, and collection title sizes
- --journal-nav-size, --journal-nav-weight, --journal-nav-tracking, --journal-nav-transform: navigation typography
- --journal-space-section: spacing between moments; themes derive smaller spacing values from it
- --journal-space-block, --journal-space-element, --journal-space-item, --journal-space-tight: spacing inside moments and other components
- --journal-photo-gap: gap between photos
- --journal-profile-base: profile photo base size; --journal-profile-display: none hides it
- --journal-photo-arrangement: flat switches from masonry to one grid; --journal-photo-columns sets the number of grid columns
- --journal-measure, --journal-inset, --journal-inset-md: shared content width and horizontal page padding
- --journal-masthead: condense enables the built-in condensed header; --journal-scrolled-after and the --journal-bar-* variables adjust its threshold and layout
Light and dark mode
Reader's choice follows the visitor's device preference and displays a switch in the footer. Add the .dark prefix to a selector when an override should apply only in dark mode.
Set --journal-color-scheme to light or dark on :root to use one mode for every visitor. The journal applies the matching generated palette and hides the footer switch. Remove that declaration to return to Reader's choice.
.dark .journal-hero {
background: color-mix(in oklch, var(--accent) 8%, transparent);
}:root {
/* Applies the dark palette and hides the footer switch. */
--journal-color-scheme: dark;
/* Optional overrides for the generated dark palette. */
--background: #0b0a09;
--foreground: #ece5d9;
}Generate CSS with an AI assistant
The prompt below describes the supported page structure, selectors, variables, and safety limits. Add your theme name and design request, then send it to a capable AI assistant. Review the result before pasting it into Custom CSS.
For later changes, include your current stylesheet and ask for one specific update. Check the desktop, mobile, light, and dark previews before saving.
You are writing custom CSS for a photography journal on Moments
(moments.im). Read all of this before writing any CSS, then follow the
brief at the end.
HOW THE STYLESHEET IS APPLIED
- The page loads three stylesheets in order: a color palette, the
journal's chosen theme, then this custom CSS. Custom CSS comes last,
so plain selectors win. Avoid !important unless a rule truly cannot
win without it.
- Write plain, standard CSS. No preprocessors, no scripts, no HTML.
- @import is allowed only over https from the supported font services.
url() values may use https, a relative path on the journal, or an
inline data:image.
- Readers can choose light or dark. Base rules are light mode; prefix
dark-mode overrides with .dark (for example .dark .journal-hero).
Style both, or pin one mode with --journal-color-scheme: light or dark
on :root. Pinning a mode hides .journal-theme-switcher automatically.
THE STRUCTURE OF EVERY PAGE
main.journal-main.journal-view-{home|moment|page|collection}
section.journal-hero the journal's header, on every view
img.journal-hero-image only when a hero photograph is set
div.journal-hero-scrim darkens that photograph for the text
div.journal-hero-glow a soft accent light
div.journal-hero-inner the column: avatar, name, bio, nav
div.journal-avatar > img
h1|p.journal-name > a an h1 on the front page only
div.journal-bio
nav.journal-nav > a ...
home: section.journal-feed > div.journal-feed-list >
article.journal-moment ... one per moment, newest first
moment page: article.journal-main-moment.journal-moment
header.journal-moment-header
div.journal-moment-meta > h1.journal-moment-title
+ p.journal-moment-count
div.journal-moment-tags > span.journal-moment-place
+ span.journal-moment-date + span.journal-tag ...
section.journal-photos-section > div.journal-photos >
a.journal-photo > img
section.journal-note > div.journal-note-text
section.journal-conversation replies from the open web
written page: article.journal-page > div.journal-page-body
collection: h1.journal-collection-title, then a feed or a gallery
footer.journal-footer
div.journal-footer-credit, div.journal-footer-links,
div.journal-theme-switcher
A feed item is the same article.journal-moment with an h2 title inside
a link, a three-line clamp on .journal-note-text, and a.journal-more
linking through to the moment. Scope list-only rules as
.journal-feed .journal-moment; a bare .journal-moment rule also
dresses the moment's own page.
FACTS THE MARKUP STATES (style by content; nothing reads them by default)
- article.journal-moment carries data-photos="N", data-tags="slugged
tags" (match one with [data-tags~="film"]), data-month="01".."12",
and --moment-photos: N for calc().
- a.journal-photo carries data-orientation="portrait|landscape|square"
and --tile-aspect, its natural width / height.
VARIABLES THE PAGE READS (set on :root unless one element should differ)
- --background, --foreground: the page and its text
- --primary, --accent: the accent as the page uses it;
--journal-accent is the owner's chosen color before the theme
adjusted it
- --border, --radius, --journal-border-width
- --journal-color-scheme: light or dark pins one mode
- --journal-align and --journal-align-margin: header/content alignment
- --journal-hero-background: plain, glow, wash, dawn, or aurora
- --journal-heading-font, --journal-body-font
- --journal-title-size (the journal name), --journal-moment-title-size
- --journal-collection-title-size
- --journal-heading-weight, --journal-heading-tracking,
--journal-body-weight
- --journal-space-section: air between moments in the feed
- --journal-section-gap: air between the header and what follows
- --journal-space-block: air between a moment's parts
- --journal-photo-gap: the gutter between photographs
- --journal-profile-base: the portrait's size (mobile derives from it);
--journal-profile-display: none removes it
- --journal-hero-py: the header's vertical padding on wide screens
RULES THAT KEEP IT WORKING
- Some sizes are read from variables on the element (the journal name,
the moment title). Change the variable, not font-size, or nothing
happens.
- With three or more photographs the grid is a masonry of columns, so
frames are grandchildren of .journal-photos. Set
:root { --journal-photo-arrangement: flat; --journal-photo-columns: 3 }
to make every frame a direct child in the order they were taken.
- Photographs load into placeholders. In a flat grid, give geometry to
.journal-photos > * rather than .journal-photo, so the held space
matches and nothing jumps.
- A flat grid of mixed shapes leaves holes. Crop it to uniform cells
with .journal-photos > * { aspect-ratio: 1 }, or leave it masonry.
- With a hero photograph the header text turns white over the image.
If you restyle the header, keep
.journal-hero:has(.journal-hero-image) readable: a scrim, light
type, or both.
- The owner may center the header (--journal-align: center answers
it). Make sure your layout survives both alignments.
- Moments can contain one photo or many, a title or no title, and a field
note or no field note. Style for all of these cases.
OUTPUT
Return one complete stylesheet, grouped by short comments, and nothing
else. Write only rules that change something; do not restate the
theme.
MY BRIEF
Theme I am starting from: [theme name]
The look I want: [describe it in your own words: moods, references,
typefaces, how the photographs should hang. The more concrete, the
better.]Below is the stylesheet my Moments journal uses today. Keep its
character and everything you are not asked to touch. Change only
this: [what you want different]. Return the complete updated
stylesheet and mark what changed with a short comment.
[paste your stylesheet here, from the Custom CSS editor]AI-generated CSS passes through the same checks as CSS written by hand. Unsupported imports, URLs, and script-like declarations are removed. Always review the preview before saving.
CSS safety checks
Custom CSS is checked every time the journal renders. Closing style tags, legacy script-capable CSS declarations, unsupported imports, and unsafe URLs are removed or neutralized. The editor displays a notice when it changes the stylesheet.
Font imports are allowed only over HTTPS from Google Fonts, Bunny Fonts, and Adobe Fonts hosts. Other url() values may use HTTPS, a relative path on the journal, or an inline data:image. Plain HTTP, protocol-relative URLs, and script URLs are blocked.
Custom CSS is applied only to your journal and cannot change another journal.
A photograph or texture can be used as a background if it loads over https, or is embedded inline as a base64 data:image.
If Moments Plus access ends
Your hand-written stylesheet and selected Moments Plus theme remain saved but are not applied. The journal uses the free base theme associated with the selected Moments Plus theme. Fine-tuning CSS remains active on every plan. The saved Moments Plus theme and hand-written CSS are applied again when access returns.
Keep reading
Related guide
Journal identity and appearance
Shape the name, introduction, color, typography, spacing, header, and light or dark character of your journal.
Related guide
Everything in Moments Plus
Explore every feature of Moments Plus, the larger photo allowances, and what happens to your work if paid access ends.
Related guide
Begin a photo journal
Create a home for your photographs, choose its address and character, then publish your first moment.