CSS @keyframes: what actually animates
@keyframes pulse {
0% { transform: scale(1, 1); }
50% { transform: scale(1.08, 1.08); }
100% { transform: scale(1, 1); }
}
.pulse {
animation-name: pulse;
animation-duration: 0.8s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
Adding the class to a loaded view is enough to start it. Shorthand order: animation: name duration timing-function delay iteration-count.
The animatable set — everything else is silently dropped
Exactly these properties animate: width, height, opacity, background-color, rotate, rotateX, rotateY, perspective, scaleX, scaleY, translateX, translateY — plus the transform shorthand. A keyframe declaring color, border-radius, or font-size is discarded with no warning: the animation "runs" but that property never moves. If a keyframe animation does nothing, check the property list first.
Rules the web trained you to get wrong
- No
fill-mode → snap-back. Without animation-fill-mode: forwards (or both), every animated value resets to unset when the animation ends. If the view must stay where it landed, forwards is mandatory.
- A unitless delay is SECONDS:
animation-delay: 1.5 = 1500ms (durations: 4s = 4000ms, 500ms works too).
animation-direction recognizes only the exact string reverse — alternate and alternate-reverse are ignored.
animation-play-state is not implemented (a TODO in the parser) — pausing/resuming must be done in code.
infinite maps to Number.POSITIVE_INFINITY for iteration-count.
spring is a valid NativeScript-only timing function.
transform: none in a keyframe resets scale to 1 (identity), not 0.
Structural quirks
- Duplicate
@keyframes names replace wholesale — the second block wins entirely; there is no per-offset merging.
- Comma selectors (
20%, 60% { }) expand into separate keyframes, and the resulting array is sorted by offset regardless of source order.
- Keyframes gated behind a non-matching
@media yield null, not [] — code reading page.getKeyframeAnimationWithName(...) / parsed animation.keyframes must null-check.
@import 'path/other.css'; works in NativeScript CSS.
- Replaying an already-running keyframe animation logs
Keyframe animation is already playing. and returns an already-resolved promise (unlike view.animate(), which rejects — see ns-view-animations).
- Keyframe values write through the CSS
keyframe value source — they compete with styles set from code; prefer one owner per property.
Verified 2026-08 against @nativescript/core 9.1.0-rc.3: the animatable-property gate and shorthand handlers confirmed in packages/core/ui/styling/css-animation-parser.ts and keyframe-animation.ts; unitless delay, spring curve, infinite mapping, duplicate-name replacement and null-under-media asserted in apps/automated/src/ui/animation/css-animation-tests.ts. Not re-run standalone.
1---2name: ns-css-keyframes3description: Use when a CSS @keyframes animation animates nothing, snaps back at the end, or ignores animation-direction alternate / animation-play-state — only 12 properties (+transform) are animatable and everything else is silently dropped, fill-mode forwards is required to keep the end state, a unitless animation-delay is seconds, and keyframes under a non-matching @media come back null.4license: Apache-2.05---67# CSS @keyframes: what actually animates89```css10@keyframes pulse {11 0% { transform: scale(1, 1); }12 50% { transform: scale(1.08, 1.08); }13 100% { transform: scale(1, 1); }14}15.pulse {16 animation-name: pulse;17 animation-duration: 0.8s;18 animation-timing-function: ease-in-out;19 animation-iteration-count: infinite;20}21```2223Adding the class to a loaded view is enough to start it. Shorthand order: `animation: name duration timing-function delay iteration-count`.2425## The animatable set — everything else is silently dropped2627Exactly these properties animate: `width`, `height`, `opacity`, `background-color`, `rotate`, `rotateX`, `rotateY`, `perspective`, `scaleX`, `scaleY`, `translateX`, `translateY` — plus the `transform` shorthand. A keyframe declaring `color`, `border-radius`, or `font-size` is **discarded with no warning**: the animation "runs" but that property never moves. If a keyframe animation does nothing, check the property list first.2829## Rules the web trained you to get wrong3031* **No `fill-mode` → snap-back.** Without `animation-fill-mode: forwards` (or `both`), every animated value resets to unset when the animation ends. If the view must stay where it landed, `forwards` is mandatory.32* **A unitless delay is SECONDS**: `animation-delay: 1.5` = 1500ms (durations: `4s` = 4000ms, `500ms` works too).33* **`animation-direction` recognizes only the exact string `reverse`** — `alternate` and `alternate-reverse` are ignored.34* **`animation-play-state` is not implemented** (a TODO in the parser) — pausing/resuming must be done in code.35* `infinite` maps to `Number.POSITIVE_INFINITY` for `iteration-count`.36* `spring` is a valid NativeScript-only timing function.37* `transform: none` in a keyframe resets scale to **1** (identity), not 0.3839## Structural quirks4041* **Duplicate `@keyframes` names replace wholesale** — the second block wins entirely; there is no per-offset merging.42* Comma selectors (`20%, 60% { }`) expand into separate keyframes, and the resulting array is sorted by offset regardless of source order.43* **Keyframes gated behind a non-matching `@media` yield `null`, not `[]`** — code reading `page.getKeyframeAnimationWithName(...)` / parsed `animation.keyframes` must null-check.44* `@import 'path/other.css';` works in NativeScript CSS.45* Replaying an already-running keyframe animation logs `Keyframe animation is already playing.` and returns an already-resolved promise (unlike `view.animate()`, which rejects — see `ns-view-animations`).46* Keyframe values write through the CSS `keyframe` value source — they compete with styles set from code; prefer one owner per property.4748Verified 2026-08 against @nativescript/core 9.1.0-rc.3: the animatable-property gate and shorthand handlers confirmed in packages/core/ui/styling/css-animation-parser.ts and keyframe-animation.ts; unitless delay, spring curve, infinite mapping, duplicate-name replacement and null-under-media asserted in apps/automated/src/ui/animation/css-animation-tests.ts. Not re-run standalone.