DOCUMENTATION

Custom CSS

Last updated September 2026
Freeform
Paid plans

Every item has an Advanced (custom CSS) section at the bottom of Properties → Design. It lets you set any CSS property on the item directly - for the few looks that don't have a control yet.

✦
Try the controls first. Fills, gradients, gradient and outlined text, letter spacing, shadows, blur, shapes, faded edges and looping animations all have controls (see Looks & effects and Animation). Anything a control sets also appears in Advanced, so it's a good way to learn the CSS behind a look.

Quick start

1
Select the item

Click it on the canvas, or its row in the Layers panel.

2
Open Advanced

Properties → Design, scroll to the bottom, open Advanced (custom CSS).

3
Type the CSS as JSON

For example, a soft coloured glow around the text:

{ "textShadow": "0 0 24px rgba(99, 102, 241, 0.8)" }
4
Click outside the box

It applies when the box loses focus. If the JSON isn't valid, you'll see “That is not valid JSON, so nothing was changed” and the item stays as it was.

The format

The box takes one JSON object: property names on the left, values on the right.

RuleExample
Property names are camelCase (drop the dash, capitalise the next letter).background-color → "backgroundColor"
Browser-prefixed properties start with a capital.-webkit-text-stroke → "WebkitTextStroke"
Names and text values go in double quotes."mixBlendMode": "screen"
A plain number means pixels for sizes."borderRadius": 24 is 24px
…but has no unit for opacity, line height, font weight and layer order."opacity": 0.5, "lineHeight": 1.2
Anything with another unit or a function is a quoted string."letterSpacing": "0.05em", "filter": "blur(4px)"
Commas between pairs, none after the last one.{ "opacity": 0.9, "borderRadius": 12 }

How it combines with the controls

  • Advanced is the same storage the controls use. Set a shadow with the Shadow control and it appears here as boxShadow; type a boxShadow here and the Shadow control shows it (or shows Custom if it's a shape the control can't edit, such as two shadows stacked).
  • A value typed here replaces the control's value for that property. Picking an option in a control replaces what you typed.
  • Delete a line to go back to the default.
  • The CSS goes on the item's own box - the whole item, including its padding and background.
  • The published page uses exactly the same styles as the editor.
⚠
Leave position and size to Freeform: don't set position, top, left, width, height or transform here. Freeform places, sizes and animates items with those, so setting them yourself fights with dragging, the phone layout, scroll effects and loops. Use Layout and Animate instead. (The one exception, "width": "max-content" for a sideways-scrolling strip, is set for you by the Loop control.)

Phone-only and desktop-only styles

The second box, Per-device overrides, takes styles that apply to only one version of the page: "mobile" (the phone layout, screens up to 767px wide) or "desktop". They are added on top of the item's normal styles.

{
  "mobile":  { "fontSize": 28, "letterSpacing": "-0.01em" },
  "desktop": { "textShadow": "0 2px 30px rgba(0, 0, 0, 0.4)" }
}

Hide on phone (Properties → Layout) is stored here as "mobile": { "display": "none" } - so if you clear this box, a hidden item comes back on phones.

Sizes that grow with the page

Any size can be written as fluid(smallest, at width, largest, at width). The value slides between the two as the space the item sits in gets wider, and stops at each end.

{ "fontSize": "fluid(18, 375, 32, 1440)" }

That text is 18px when its space is 375px wide, 32px at 1440px, and in between for sizes in between.

Built-in loop animations

The Loop control in the Animate tab covers these, but you can also write them as an animation value. Only these names work - you can't add your own keyframes.

NameWhat it does
cv-float-yDrifts up 14px and back.
cv-spinOne full turn.
cv-pulseGrows slightly and brightens, then settles.
cv-glowA breathing coloured shadow. Its colour is "--cv-glow".
cv-blinkOn, off (use with steps(1)).
cv-marquee, cv-marquee-revSlides sideways by half its width, for repeating strips. Needs "width": "max-content" and "whiteSpace": "nowrap" with the content typed twice (the Loop control sets both for you).
{
  "animation": "cv-glow 3s ease-in-out 0.5s infinite",
  "--cv-glow": "rgba(236, 72, 153, 0.5)"
}

The parts are: name, how long one loop takes, speed curve, an optional start delay, then infinite (and optionally reverse).

Recipes

LookCSS
Two shadows stacked (lifted card with a fine highlight)"boxShadow": "0 20px 60px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.08)"
Glowing text"textShadow": "0 0 20px #8b7bff, 0 0 40px #8b7bff"
Colour wheel / conic gradient"backgroundImage": "conic-gradient(from 0deg, #f43f5e, #f59e0b, #10b981, #3b82f6, #f43f5e)"
Blend into what’s behind"mixBlendMode": "screen"
Black-and-white image"filter": "grayscale(1) contrast(1.1)"
Your own shape"clipPath": "polygon(0 0, 100% 10%, 100% 100%, 0 90%)"
Several effects at once"filter": "blur(2px) saturate(1.4)"
Text with a tiny cut-out look"WebkitTextStroke": "1px #ffffff", "color": "transparent"

What custom CSS can't do

  • No hover, focus or other states - the styles always apply. (Buttons have hover effects built into their Style setting.)
  • No selectors, ::before/::after, @media or @keyframes - only properties of the item itself. Use Per-device overrides instead of @media, and the built-in loops instead of your own keyframes.
  • Images in url(...) must be a public https:// address.
  • Styling inside text (one word bold or coloured) is done with the text's own formatting bar, not here.

Troubleshooting

ProblemFix
“That is not valid JSON, so nothing was changed.”Check for single quotes (use double), a missing comma between pairs, a comma after the last pair, or a missing { or }.
Nothing changesCheck the property name is camelCase and spelt correctly - an unknown name is ignored by the browser. Check the value is valid CSS for that property.
A control shows “Custom”The value came from here and the control can’t edit that shape. Edit it here, or pick an option in the control to replace it.
A frame’s fill doesn’t showA frame paints its own fill on top of a CSS background. Use the Fill control (Properties → Appearance): it sets the fill and clears the frame’s own.
It looks different on phonesCheck Per-device overrides for a "mobile" entry.