Custom CSS Properties

Animate any CSS property using custom key-value pairs and the advanced code field.

The from and to objects accept any animatable CSS property — not just named shortcuts like opacity or scale. Pass properties in camelCase (boxShadow) or as a quoted kebab-case string ('box-shadow'), and the SDK animates them directly.

Basic Usage

Add any CSS property directly to from or to:

typescript
import { Motion } from "@motion.page/sdk";

Motion("card-hover", ".card", {
  to: {
    boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)",
    outline: "2px solid #6633EE",
    outlineOffset: "4px",
  },
  duration: 0.3,
  ease: "power2.out",
}).onHover({ each: true, onLeave: "reverse" });

Quoted kebab-case strings work identically — both forms map to the same CSS property:

typescript
Motion("shadow-in", ".card", {
  to: { "box-shadow": "0 8px 24px rgba(0, 0, 0, 0.15)" },
  duration: 0.3,
  ease: "power2.out",
}).onHover({ each: true, onLeave: "reverse" });

CSS Custom Properties (Variables)

Animate CSS custom properties (CSS variables) using a quoted '--property-name' key:

typescript
Motion("palette-shift", ":root", {
  from: { "--brand-hue": "220" },
  to: { "--brand-hue": "300" },
  duration: 1,
}).onScroll({ scrub: true });
css
@property --brand-hue {
  syntax: "<number>";
  initial-value: 220;
  inherits: true;
}

.button {
  background: hsl(var(--brand-hue), 70%, 55%);
}

Registering the variable with @property tells the browser its syntax type, enabling smooth CSS-level transitions. Without registration, the SDK still sets the value, but the browser cannot interpolate it — simpler numeric-like strings (e.g. '220', '0.5') that the SDK can parse directly will still tween correctly regardless.

String vs Numeric Values

ValueBehaviorExample
NumberThe SDK appends px for length properties; unitless for othersborderRadius: 88px
StringPassed verbatim — required for units other than px or multi-value propertiesborderRadius: '50%', boxShadow: '0 4px 12px #000'

Always use a string for:

  • Multi-value properties: boxShadow, textShadow, backdropFilter, backgroundImage
  • Non-px units: '1.5rem', '50%', '0.1em'

Always use a number for:

  • Unitless properties: opacity, zIndex
  • Properties where px is the correct unit and you don’t need to specify it: outlineOffset: 4

Commonly Used Custom Properties

Properties beyond the named SDK shortcuts that animate cleanly:

PropertycamelCaseExample Value
Box shadowboxShadow'0 8px 24px rgba(0,0,0,0.15)'
Text shadowtextShadow'0 2px 8px rgba(0,0,0,0.4)'
Backdrop filterbackdropFilter'blur(12px)'
Outlineoutline'2px solid #6633EE'
Outline offsetoutlineOffset'4px'
Line heightlineHeight'1.6'
Max widthmaxWidth'800px'
Border colorborderColor'rgba(102, 51, 238, 0.6)'
Border widthborderWidth'2px'
Column gapcolumnGap'32px'
Object positionobjectPosition'50% 0%'
Padding (single side)paddingTop'24px'
CSS variable'--my-var''220'

Named shortcuts like borderRadius, letterSpacing, fontSize, filter, clipPath, backgroundColor, color, width, and height are already part of AnimationVars and work exactly the same way — no special handling needed.

Common Patterns

Box Shadow Lift on Hover

The classic card lift effect — start from no shadow and animate to a deep shadow on hover:

typescript
Motion("lift", ".card", {
  to: { boxShadow: "0 16px 32px rgba(0, 0, 0, 0.18)" },
  duration: 0.3,
  ease: "power2.out",
}).onHover({ each: true, onLeave: "reverse" });

Because boxShadow: none (no shadow) is the element’s natural CSS state, omitting from lets the SDK read it automatically.

Frosted Glass Reveal

Animate a backdrop blur overlay into view on scroll:

typescript
Motion("glass", ".overlay", {
  from: {
    backdropFilter: "blur(0px)",
    opacity: 0,
  },
  to: {
    backdropFilter: "blur(16px)",
    opacity: 1,
  },
  duration: 0.6,
  ease: "power2.out",
}).onScroll({ scrub: false, toggleActions: "play none none none" });

Glowing Text Shadow on Scroll

Drive a text glow effect with scroll progress:

typescript
Motion("glow", ".headline", {
  from: { textShadow: "0 0 0px rgba(102, 51, 238, 0)" },
  to: { textShadow: "0 0 32px rgba(102, 51, 238, 0.8)" },
  duration: 1,
}).onScroll({ scrub: true });

For shadow interpolation to tween smoothly, keep the same number of shadow layers in both from and to. Mismatched layer counts cause a hard swap at the midpoint.

CSS Variable Theme Shift

Drive a hue-based color scheme with scroll, affecting every element that references the variable:

typescript
Motion("hue-scroll", ":root", {
  from: { "--accent-hue": "200" },
  to: { "--accent-hue": "320" },
  duration: 1,
}).onScroll({ scrub: true });
css
@property --accent-hue {
  syntax: "<number>";
  initial-value: 200;
  inherits: true;
}

a, button, .badge {
  color: hsl(var(--accent-hue), 80%, 60%);
}

Multi-Property Transition (Timeline)

Chain custom properties across multiple animation steps:

typescript
Motion("reveal-card", ".card", [
  {
    target: ".card",
    from: { opacity: 0, boxShadow: "0 0px 0px rgba(0,0,0,0)" },
    to: { opacity: 1, boxShadow: "0 8px 24px rgba(0,0,0,0.12)" },
    duration: 0.4,
    ease: "power2.out",
  },
  {
    target: ".card",
    to: { borderRadius: "20px", outlineOffset: "6px" },
    duration: 0.3,
    ease: "power2.inOut",
  },
]).onScroll({ scrub: false, toggleActions: "play none none none" });

In the Motion.page Builder

In the builder, the Custom Property field lets you add any animatable CSS property as a key-value pair. To remove a custom property in column view, right-click the property row and choose Remove from the context menu.

Click the code field icon to switch between the standard input and the Advanced Code Field. The Advanced Code Field accepts any valid key/value pair from the animation from/to object — useful for complex multi-value strings like boxShadow or backdropFilter that are difficult to express in a single-value input.

Notes

  • Use camelCase (boxShadow) or a quoted kebab-case string ('box-shadow') — both resolve to the same property.
  • For shadow properties (boxShadow, textShadow), keep the number of shadow layers identical on both ends for smooth interpolation. Mismatched layers hard-swap at progress 0.5.
  • CSS custom properties ('--my-var') with numeric string values (e.g. '220') tween correctly without @property. Use @property registration when you need CSS-level fallback interpolation or when the value is not a simple number.
  • Not every CSS property can be animated — only properties the browser can interpolate (numeric, color, or parseable values). Properties like display, position, or font-family cannot be tweened.
  • Custom properties work alongside all named shortcuts in the same from/to object — mix freely.