Skip to content

Features

Included on every plan.

Instant sync

Edits reach every device at once.

Stays private

Encrypted on your device first.

Works offline

Keep writing; it catches up later.

Keyboard first

Every action has a shortcut.

Shared layouts

One template for the whole team.

Version history

Every save, back to the first.

Any language

Thirty languages in one interface.

Fast search

Finds notes as you type.

Locked notes

A passcode for the private ones.

Share a link

Anyone with the link can read.

Tags

Sort notes any way you like.

Export anytime

Markdown, PDF or plain text.

Stagger

Scroll

Scroll-triggered

An animation that plays once when an element scrolls into view.

The element waits just out of sight. When enough of it enters the viewport, it plays an ordinary timed transition, with its own duration and easing, and finishes no matter what the scroll does next. It plays once: scrolling back up leaves it where it is, which is what separates it from scroll-linked motion. Ease-out at 300 to 500ms is right for an entrance; the other easing options are there so you can feel why.

Also called reveal on scroll, animate on scroll, in-view animation, scroll reveal, fade in when scrolled to, appear as you scroll

Use when

  • Marketing and editorial pages, where sections arriving one after another set the pace of the story.
  • A grid of features or cards further down a long page, with a short stagger so each row arrives as a group.
  • Drawing the eye to one figure, quote or result as the reader reaches it.

Avoid when

  • App UI people need right away, like tables, dashboards, search results and settings. Waiting for content to fade in slows the work down.
  • Every element on the page. When everything reveals, nothing stands out, and fast scrolling turns into waiting.
  • Motion that should follow the scrollbar and reverse when scrolling back. That is scroll-linked.

Prompt

Reveal each card once as it scrolls into view.

- Inner box: `opacity` 0 to 1, `translateY(14px)` to `none`, 500ms `var(--ease-out)` (`cubic-bezier(0.23, 1, 0.32, 1)`). Transition only those.
- Observe the card itself (it never moves; only the inner box animates) with `threshold: 0.15`. On intersect: `data-in="true"`, `unobserve`.
- Each callback: keep `isIntersecting` entries with `intersectionRatio >= 0.15`, skip entries already revealed (focus, first paint), DOM order; delay = min(index, 4) × 60ms, cleared on `transitionend`.
- First paint: cards with any pixel on screen show without animating: run the pass at DOMContentLoaded and again at `load` (`#hash` jumps), with inline `transition: none` on the inner box for that pass, restored after a reflow.
- Observer script (end of `<body>` or deferred): mark those and cards scrolled past with `data-in="true"`, then add `js` to `<html>`; hide `.js .reveal:not([data-in="true"]) > .reveal-inner`, transition only when revealed.
- Cards are in the initial HTML (server-rendered or static), keeping their space.
- Motion/SSR: `initial` is in the server HTML; add `<noscript>`, print and 3s-failsafe styles forcing `.reveal-inner` visible.
- `focusin` on an unrevealed card: reveal now, no delay.
- `@media print`: all cards visible, no transition.
- Reduced motion: no transform, opacity over 150ms, no JS delays.

Acceptance checklist

  • Cards in view when the page loads are visible from the first frame and do not animate.
  • Each card below animates the first time it is about 15% inside the viewport, then stays.
  • Scrolling back up never replays a card that has already revealed.
  • One card takes 500ms from hidden to rest.
  • Cards that enter together start 60ms apart in reading order, and none waits more than 240ms.
  • After an instant jump (anchor link or `scrollTo` without smooth), every card in view is fully visible.
  • Cards hold their space before they reveal; nothing below them moves.
  • With JavaScript off, or if the observer script fails to load, every card is visible (the cards must be in the initial HTML).
  • Moving focus into a card that has not revealed starts its reveal immediately, with no stagger delay.
  • With reduced motion on, cards appear without moving and without a stagger, over at most 150ms.
  • Printing the page shows every card.

Code

:root { --ease-out: cubic-bezier(0.23, 1, 0.32, 1); }

/* Markup: <div class="reveal"><div class="reveal-inner">…</div></div>
   .reveal is observed and never moves; .reveal-inner animates. The JS tab adds
   .js last, so on first paint, or with JavaScript off, nothing is hidden. */
.js .reveal:not([data-in="true"]) > .reveal-inner {
  opacity: 0;
  transform: translateY(14px);
}

/* The transition lives on the revealed state only, so hiding is instant. */
.js .reveal[data-in="true"] > .reveal-inner {
  transition-property: opacity, transform;
  transition-duration: 500ms;
  transition-timing-function: var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .js .reveal:not([data-in="true"]) > .reveal-inner { transform: none; }
  .js .reveal[data-in="true"] > .reveal-inner {
    transition-property: opacity;
    transition-duration: 150ms;
  }
}

@media print {
  .js .reveal:not([data-in="true"]) > .reveal-inner { opacity: 1; transform: none; }
  .js .reveal-inner { transition: none !important; }
}

Accessibility

With JavaScript off, or when the script fails, every card must be visible: hide cards only from the script that reveals them, and never the ones already on screen. Focus moving into a card that has not revealed starts its reveal at once. Under reduced motion, show content with a 150ms opacity fade, without movement or stagger.

  • Scroll-linked

    An animation whose progress is tied directly to the scroll position, moving forward and backward as you scroll.

  • Stagger

    Several items animate one after another with a small delay between each, creating a cascade.

  • Enter and Exit

    The animations an element plays when it is added to the screen and when it is removed.

Often confused with

  • Scroll-triggered vs Scroll-linkedScroll-triggered plays once on its own clock when something enters view. Scroll-linked has no clock: its progress is the scroll position, so it runs backwards when you scroll up.