Skip to content

CSS Syntax

Nest accepts CSS selector strings wherever a selector is expected. They are parsed at evaluation time and matched by the same engine as programmatic selectors.

Every form below is exercised by the selectors-demo template, whose self-tests assert exactly which nodes each selector matches.

Matches any node:

is = "*"

Matches a node by its name attribute (the attrset key in the DOM):

is = "#lb-prod" # node named lb-prod
is = "#web-1" # node named web-1

Matches by name, or by a trait whose path ends in that word:

is = "web" # node named web, or carrying a trait ending in `.web`

Matches nodes whose entity trait exposes an output class with this name:

is = ".nixos" # entity has nixos output class
is = ".homeManager" # entity has homeManager output class

Matches nodes where toString node.key == value:

is = "[env=prod]"
is = "[system=x86_64-linux]"

Boolean attributes are understood: [key=true] / [key=false] match a real true/false as well as the strings "true"/"false".

Matches nodes that have the attribute, regardless of value:

is = "[sshKeys]"
is = "[httpPort]"

& is the node currently being matched. On its own it matches anything; its value is as an anchor for relative selectors (see below):

is = "&.nixos" # this node, and it has the nixos class
is = "& > .web" # this node, and it has a direct .web child

Multiple tokens without a combinator — all must match (AND):

is = "#lb-prod.nixos" # named lb-prod AND has nixos class
is = "[env=prod][system=x86_64-linux]" # two attribute conditions

A space between selectors matches a node descended from another (not necessarily a direct child):

is = "prod web" # a `web` anywhere under `prod`

Matches a node that is a direct child of another:

is = "prod > web" # node web whose parent is prod
is = "cluster > *" # any direct child of cluster

Matches a node whose immediately-preceding sibling matches the left selector (siblings are ordered by their DOM key):

is = "#web-1 + #web-2" # web-2, when web-1 is the sibling just before it

Comma-separated alternatives — any must match:

is = "#web-1, #web-2"
is = "[env=prod], [env=staging]"

Matches nodes NOT matching sel:

is = ":not(#alice)"
is = ":not([env=staging])"

Matches nodes that have a descendant matching sel:

is = ":has(.admin)"
is = ":has([sshKeys])"

Matches nodes matching sel; with a separator-less list it is an OR over the alternatives:

is = "box:is(#web-1#web-2)" # a box that is web-1 OR web-2

Matches nodes that have an ancestor matching sel:

is = ":within(#prod)"
is = ":within(.nixos)"

Combinators anchored on & describe the matched node’s surroundings:

is = "& > .web" # I have a direct .web child (like :has(> .web))
is = "&:has(.admin)" # I have an .admin descendant
# All hosts
is = ".nixos"
# Hosts named web-1
is = "#web-1.nixos"
# Prod hosts by attribute
is = "[env=prod].nixos"
# Either prod or staging
is = "[env=prod], [env=staging]"
# Hosts with admin descendants
is = ".nixos:has(.admin)"
# Web nodes under prod
is = "prod .nixos[addr]"

String selectors are parsed by parseCssSel which tokenizes via parseCompound:

  • #name{ __sel = "id"; name = "…" }
  • .class{ __sel = "class"; name = "…" }
  • &{ __sel = "current" }
  • [k=v]{ __sel = "attr"; key = "k"; val = "v" }
  • [k]{ __sel = "attrExists"; key = "k" }
  • :pseudo(inner){ __sel = "pseudo"; selector = parsed-inner }
  • ,{ __sel = "or"; selectors = [ … ] }
  • (space) → { __sel = "descendant"; ancestorSel = …; descendantSel = … }
  • >{ __sel = "child"; parentSel = …; childSel = … }
  • +{ __sel = "adjacent"; previousSel = …; nextSel = … }

Compound (multiple tokens) → list, matched as AND.

Contribute Community Sponsor