Moments handbook

Customize your journal with CSS

Shape your journal11 sections · Complete guide

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.

01

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.

02

Common changes

Set shared variables on :root to change the whole journal. The examples below change only one part of the selected theme.

Bring in a font and use it for your name and titles
@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. */
}
Increase the space between moments
:root {
  --journal-space-section: 9rem;
}
Set the primary accent color
:root {
  --primary: #7c4dbd;
  --accent: #7c4dbd;
}
03

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.

A moment's own page, reduced to its named parts
<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>
The journal's front: the same moment, sitting in a list
<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>
04

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.

Center the header
.journal-hero-inner {
  text-align: center;
}

.journal-avatar,
.journal-bio {
  margin-inline: auto;
}

.journal-nav {
  justify-content: center;
}
Put the navigation above your name instead of below it
.journal-nav {
  order: -1;
  margin-top: 0;
  margin-bottom: var(--journal-gap-nav);
}
Fix the header to the side on wide screens
@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);
  }
}
Turn a moment's photographs into a strip you scroll sideways
.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;
}
05

Add generated labels or numbering

CSS counters and pseudo-elements can add visual labels without changing the stored moment content.

Number each moment as a chapter, in the margin
.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);
}
Keep a moment's title in view while its photographs pass
.journal-moment-header {
  position: sticky;
  top: 0;
  z-index: 5;
  background: linear-gradient(
    to bottom,
    var(--background) 65%,
    transparent
  );
}
06

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
Make the header full-screen and align its content to the bottom
.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
Keep the header small everywhere except the journal's front
.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
Two moments to a row on a wide screen
@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
Film-tagged moments get a darkroom border, single photos hang larger
.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
Enable a condensed header on wide screens
@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
Stack the count above the title as a quiet label
.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
Set the place in italics and outline the tags
.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
Crop every photograph to the same square
.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
Set your writing narrow, with an opening capital
.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
Set the conversation on its own quiet card
.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
A wider measure for pages, and a quieter footer
.journal-page-body {
  max-width: 46rem;
  font-size: 1.0625rem;
}

.journal-footer {
  border-top: 0;
  opacity: 0.55;
}
07

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
08

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.

A softer header in dark mode only
.dark .journal-hero {
  background: color-mix(in oklch, var(--accent) 8%, transparent);
}
Use dark mode for every visitor
: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;
}
09

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.

The base prompt. Copy it whole, then add your brief at the end
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.]
Refine a stylesheet you already have
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]
10

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.

11

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