Component
entrance
Demo
tokens.css,
base.css and entrance.css. No scripts.
Source
Copy both files, or run npx nojsui add entrance.
Browser support
Works across current and earlier versions of every major engine.
| Feature | Baseline | Chrome | Edge | Firefox | Safari | Chrome Android | Firefox Android | Safari iOS |
|---|
| Feature | Baseline | Chrome | Edge | Firefox | Safari | Chrome Android | Firefox Android | Safari iOS |
|---|---|---|---|---|---|---|---|---|
| sibling-count() and sibling-index() | limited | 138 | 138 | — | 26.2 | 138 | — | 26.2 |
Usage
A view() timeline measures an element’s position in the scrollport, so it
never fires for content that is already on screen when the page loads. That
makes .sk-reveal structurally unable to animate a hero — and a hero arriving
is the single most common reason a static marketing site still ships an
animation runtime. .sk-entrance is that moment, on the ordinary CSS
animation clock instead of a scroll timeline.
It is a utility, not a wrapper: put the class on the thing that should arrive.
<h1 class="sk-entrance">…</h1>
<h1 class="sk-entrance" data-entrance="slide-up">…</h1>
<h1 class="sk-entrance" data-entrance="scale">…</h1>
data-entrance | Effect |
|---|---|
| (omitted) | Fades in. Opacity only, nothing moves |
slide-up | Fades in while rising by --sk-entrance-distance |
scale | Fades in while growing from --sk-entrance-scale-from |
The hero look in the demo comes from three rules at the bottom of the stylesheet that have nothing to do with the entrance. Delete them when applying this to your own markup; the animation does not care what it is attached to.
The asymmetry with reveal
reveal’s stagger shifts animation-range-start, because a scroll-driven
animation has no wall clock — animation-delay does nothing to it, so the
stagger step has to move where in the range each child starts, and that
range is expressed as a percentage.
entrance has no scroll timeline to conflict with, so animation-delay is
exactly the right tool, and --sk-entrance-step is a duration, not a
percentage. Same feature — later children arrive later — opposite mechanism,
because the two components are staggering different kinds of progress: one a
position in the scrollport, the other elapsed time. See
reveal’s README for the stagger
that has to fight a timeline with no clock; this is the one that doesn’t.
Staggering a group
Put data-sk-entrance-stagger on the parent; children pick up the sequencing
automatically.
<div data-sk-entrance-stagger>
<p class="sk-entrance" data-entrance="slide-up">…</p>
<p class="sk-entrance" data-entrance="slide-up">…</p>
<p class="sk-entrance" data-entrance="slide-up">…</p>
</div>
Sequencing comes from sibling-index(), supported in Chrome 138+ and Safari
26.2+. Without it — or in the window before it shipped — every child computes
the same delay and the group arrives together, still animated, just not
sequenced. A consumer who needs sequencing in that window can set
--sk-index per child; treat it as an escape hatch for a closing window, not
the primary API.
--sk-index is not an entrance theme knob — it has no row in the table
below and it is --sk-index, not --sk-entrance-index. A child’s position
among its siblings is a fact about the markup, not about this component, so
the property is page-level and unprefixed, the same shape as --sk-density
(ADR 0019). See ADR 0030
for why, and for the same property read by reveal elsewhere in the kit.
The attribute carries the sk- prefix — data-sk-entrance-stagger, not
data-entrance-stagger — because it lands on a parent that has no component
class of its own; an unprefixed attribute there would be a bare global
selector matching anything in a consumer’s page. data-sk-motion is the
existing precedent for that shape.
Composing with a reveal exit
.sk-entrance and .sk-reveal[data-reveal-exit] do not combine on one
element. animation-name is a single cascaded value: put both classes on the
same node and only the stylesheet that happens to cascade second wins — the
other’s animation vanishes with no error and no warning.
A hero that should both enter on load and exit on scroll needs nesting instead — an outer element for the scroll-driven exit, an inner one for the on-load entrance:
<div class="sk-reveal" data-reveal-exit="slide-up">
<div class="sk-entrance" data-entrance="slide-up">…</div>
</div>
reveal’s README documents this same recipe from the other side.
How it works
@media (prefers-reduced-motion: no-preference) {
.sk-entrance {
animation-name: sk-entrance-fade;
animation-duration: var(--_duration);
animation-timing-function: var(--sk-ease-out);
animation-fill-mode: both;
}
@keyframes sk-entrance-fade { from { opacity: 0; } }
}
No @supports guards the animation itself: CSS animations are Baseline
widely available, so unlike reveal there is no engine where these keyframes
fail to run. animation-fill-mode: both holds the from-state through the
delay, which is what makes a stagger read as deliberate rather than as a slow
page.
Everything that hides an element lives inside @keyframes, which are only
ever attached inside the reduced-motion guard — the base state, outside that
guard, is always the visible one.
Theming
Set any of these anywhere above the component — :root, a section wrapper, or
one instance. entrance.css only ever reads them, so the nearest declaration
wins (ADR 0011).
| Property | Default | Controls |
|---|---|---|
--sk-entrance-distance | --sk-space-lg | How far slide-up travels |
--sk-entrance-scale-from | 0.94 | Where scale starts |
--sk-entrance-duration | --sk-motion-slow | How long one element takes |
--sk-entrance-step | 100ms | How much later each child begins |
Keyboard contract
None. There is nothing to operate — this is decoration over static content, not an interactive control.
| Key | Behaviour |
|---|---|
| (any) | Nothing. The utility adds no interaction and no focus targets |
Verified manually in VoiceOver and NVDA: not yet — do this before marking the component done, and change this line when you have.
Accessibility notes
The animation only ever touches opacity, translate and scale. It never
touches display, visibility, content-visibility or the element’s
presence in the DOM, so entering content is in the accessibility tree from
first paint, whether or not the animation has finished.
Under prefers-reduced-motion: reduce the animation is not merely shortened —
it is never attached, so there is no residual movement to notice. Motion is
the entire feature, so reducing it means not having it.
Degradation
The floor here is empty: CSS animations are Baseline widely available, so the
entrance itself always runs, in every engine this kit tests. sibling-count
is the only enhancement — without it, a staggered group arrives together
instead of in sequence.
| Feature used | Baseline status | Behavior without it |
|---|---|---|
sibling-count | limited | Every child in a staggered group computes the same delay and arrives together — still animated, just not sequenced |