Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/davesnx/styled-ppx/llms.txt

Use this file to discover all available pages before exploring further.

styled-ppx parses CSS string literals at compile time in one of three modes depending on which extension you use. Understanding the mode your extension operates in tells you what you can write inside the string and how the output will be scoped.

Mode overview

APIModeWhat it accepts
%cx, %styled.tagDeclaration blockDeclarations + nested selectors + nested at-rules
%styled.globalStylesheetTop-level rules and at-rules as in a .css file
%cssSingle declarationOne property: value rule
If you need arbitrary control flow, unsupported properties, or runtime-generated rules, use the Array API instead of a CSS string.

Declaration blocks

%cx and %styled.tag use the declaration-block grammar. You write the body of a rule, not the outer selector wrapper — styled-ppx generates the class selector and scopes nested rules to it automatically.
display: grid;
gap: 16px;

&:hover {
  gap: 20px;
}

svg path {
  fill: currentColor;
}

@media (min-width: 768px) {
  gap: 24px;
}
In this mode you can mix:
  • Plain declarations
  • Nested selectors (using & or bare descendant selectors)
  • Nested at-rules: @media, @supports, @container
Declarations may appear before or after nested blocks.

Stylesheets

%styled.global parses a real stylesheet. Top-level selectors stay exactly as written and are not attached to any generated class.
%styled.global(`
  html, body {
    margin: 0;
    padding: 0;
  }

  @media (prefers-reduced-motion: reduce) {
    * {
      animation: none;
    }
  }
`)
Use stylesheet mode for global resets, document-level selectors, and top-level at-rules. See Global Styles for more.

Single declarations

%css accepts exactly one property: value pair — no selectors, no braces, no at-rules.
display: block
Valid in %css:
  • display: block
  • display: block; (trailing semicolon is optional)
Not valid in %css:
  • color: red; background: blue; — multiple declarations
  • &:hover { color: red; } — selector block
  • @media (...) { ... } — at-rule
  • { display: block } — outer braces
For selectors, media queries, or more than one declaration, use %cx or %styled.tag. See the %css reference for details.

Selectors and nesting

Declaration blocks support nested selectors. & stands for the current generated selector.
&:hover { color: red; }
&::before { content: ""; }
&.active { opacity: 1; }
& > span { display: inline-block; }
& + & { margin-top: 8px; }
div & { margin-left: auto; }
Bare nested selectors (without &) become descendants of the generated class:
.child { color: blue; }
svg path { fill: currentColor; }
[data-open] { opacity: 1; }
Supported selector features:
  • Type selectors: div, button, svg path
  • Class, ID, and universal selectors: .child, #main, *
  • Attribute selectors: [disabled], [type="text"], [href^="https"]
  • Pseudo-classes: :hover, :focus, :not(...), :is(...), :where(...), :has(...)
  • Pseudo-elements: ::before, ::after, ::first-line, ::first-letter
  • Combinators: descendant, >, +, ~
  • Comma-separated selector lists
More than one level of nesting may compile but will produce incorrect CSS output. Keep selector nesting to a single level.

At-rules

Declaration blocks support nested at-rules. Supported examples:
display: grid;

@media (min-width: 768px) {
  grid-template-columns: repeat(2, 1fr);
}

@supports (display: grid) {
  gap: 16px;
}

@container card (width >= 400px) {
  padding: 24px;
}
Interpolation works in at-rule preludes:
@media $(Media.mobileDown) {
  display: block;
}

@media screen and $(Media.desktopDown) {
  display: none;
}
For keyframe animations, use the dedicated %keyframe extension.

Interpolation

$(...) is supported in three positions:
  1. Declaration values — type-checked from the property’s expected type
  2. Selectors — string-like splice
  3. At-rule preludes — string-like splice
// Declaration values — typed
module Component = %styled.div(`
  color: $(textColor);
  border-bottom: 1px solid $(borderColor);
  height: calc(100vh - $(topMenuHeight));
`)

// Selector — string splice
let link = %cx(`
  &.$(activeClass) { opacity: 1; }
`)

// At-rule prelude — string splice
let box = %cx(`
  @media $(Media.tablet) { display: flex; }
`)
Only simple identifiers and module accessors are supported inside $(...). Arbitrary expressions are not supported — bind complex logic to a variable first and then interpolate the variable.

Edge cases

  • The final semicolon of the last declaration is optional.
  • %css rejects selectors, braces, and multiple declarations — use %cx for those.
  • Declaration-block APIs (%cx, %styled.tag) do not need an outer { } wrapper; stylesheet APIs (%styled.global) do use outer selectors.
  • If a property is not yet supported, the recommended escape hatch is the Array API with CSS.unsafe("-your-property", "value").

Build docs developers (and LLMs) love