Guide
Syntax overview
You write the CSS of one cell. css-doodle runs it for every cell and emits ordinary CSS.
Directives and functions start with @ or $.
Statements
A doodle is a list of statements, like the body of a CSS rule.
A { before ; makes a block; otherwise it is a declaration.
- Declarations
-
A property and a value. Names that start with
@are directives: css-doodle reads them and does not emit them as CSS. Every other property applies to every cell.@grid@size@place@gap@shape@content@seed@use - Selector blocks
-
Style the component, the grid, or an element inside each cell.
:doodleand:containerare generated once, not once per cell. A selector that begins with a colon attaches to the cell; any other is a descendant. As in CSS, a space before the colon matters:span:hoverandspan :hoverdiffer.:doodle:container:hoverspan - Conditional blocks
-
Style only the cells that match.
@mediakeeps its CSS meaning.@keyframesis hoisted.@cell(…)@nth(…)@random - Function definitions
-
A
--name(…):at the top level defines a function of your own and emits nothing.$name(…)calls it.--twice(x): $(x * 2);$twice(3)
Blocks can nest. Comments are /* … */; // comments work only inside the bodies of
@pattern, @scene, @shaders, @shape, @plot and @tile.
As in CSS, the last declaration before } does not need a semicolon.
A single-line at-rule such as @import url(fonts.css); is emitted as written
at the top level, and dropped inside a block.
@grid and background are declarations,
:doodle and :hover are selector blocks, and
@cell is a conditional block.
:doodle {
@grid: 6 / 60vmin _1px;
}
background: #eee;
transition: .2s;
@cell(random .3) {
background: #000;
}
:hover {
background: #e6437d;
scale: 1.2;
z-index: 40;
} Functions
A function sits where a CSS value would. css-doodle evaluates it again for each cell,
so @i is that cell’s index and @r(…) can be a different random
value in every cell.
@name(arguments)- A doodle function, evaluated once per cell.
$(expression)- Calc.
$(1 + 2)is3. $px(expression)- Calc, then a unit.
$px(@i * 10)is a length. $name(arguments)- A function defined with
--name().
Optional parentheses
Drop the parentheses when there are no arguments. @i means @i().
With arguments, the ( must touch the name: @r (5) is @r
followed by the text (5).
A number after the name
A number stuck to the function name becomes the first argument. Math names that already
end in a digit, such as @log2, are left alone.
| You write | It means |
|---|---|
@i5 | @i(5) |
@m5(…) | @m(5, …) |
@r360 | @r(360) |
@r1.5 | @r(1.5) |
@p1-2(…) | @p(1-2, …) |
@doodle200(…) | @doodle drawn at 200 pixels |
Composition
A dot chains functions. The rightmost one runs first:
@p.cycle(a b c) is @p(@cycle(a b c)).
The inner @ is optional.
When the function on the left has a variant of that name, the dot calls the variant instead:
@plot.scatter(star) is @plot's scatter form.
Write .@name to always compose.
@grid: 5 / 60vmin _2px;
background: #000;
border-radius: @p.cycle(100% 0 0 0); Units
A unit on an argument is kept on the result. @r(10px) and
@r(10)px both produce lengths. A leading operator on a cell function
is a modifier: @i(*10px), @x(-1), @t(%360deg).
Arguments
Separate arguments with commas. If an argument contains a comma, wrap it in parentheses or quotes.
- In pick functions,
[a-z]and[0-9]expand to a list of values. - In
@pand@pick, a colon in the first argument ends an index:@pick(@i: red, blue)alternates, and@p(@i: @palette(5))goes through the palette. ±1becomes two arguments,-1and1:@r(±10deg).--namereads that custom property when the doodle is generated.- A backtick is a double quote, so a doodle can live in a double-quoted HTML attribute.
Cell selectors
A conditional block runs only on the cells it matches. @cell can express
any of the tests below; the other names are shorter spellings. Several arguments mean
"any of these". not before the parentheses inverts the test.
A pseudo right after a selector applies to the cell, as in @nth(1)::after,
and a comma lists several selectors: @at(1, 1), @at(2, 2).
| Selector | Picks |
|---|---|
@cell | An index, a formula, an expression, or a random pick @cell(1, 3n, x = y, random .2) |
@nth | Cells by index, like :nth-child @nth(2n + 1) |
@at | The cell at a column and row @at(2, 3) |
@row, @col, @depth | A whole row, column or depth @row(even) @col(1) |
@even, @odd | A checkerboard |
@random | Cells at random. Below 1 it is a probability, 1 or more a count. @random(.3) |
@match | Cells where an expression is true @match(dr < 3) |
The first block paints both diagonals. The second rounds every cell that is not near the center.
@grid: 9 / 60vmin _1px;
background: #eee;
@cell(x = y, x + y = 10) {
background: #000;
}
@cell not (dr < 3) {
border-radius: 50%;
} Expressions
Use $(…) when a value needs arithmetic or a comparison. The same language
is accepted in @cell, @match, @random, and
@shape.
| Rule | Example |
|---|---|
Everything is a number. Custom properties drop the -- and any unit. | --w: 10px is w, read as 10 |
Inside a call, deg and turn are angles in radians. | cos(60deg) is .5 |
A % right after a number is a percent sign, unless an unsigned number, a name or ( follows it. | @tile.y > 50, 25%-5 is 20, 7%3 is 1 |
Comparisons give 1 or 0, and ignore a difference below 1e-9. | x = y, .1 + .2 = .3 is 1 |
| Values next to each other multiply. | 2x, 2π, (1 + 2)(3) |
| A power binds before a product and a sign, but a variable keeps its own sign. | -2^2 is -4, 2x^2 is 2(x^2); with --k: -2, k^2 is 4 |
| Numbers can be hexadecimal. | 0xff is 255 |
| A dashed name is one variable if it is declared, otherwise a subtraction. | a-b reads --a-b |
&& and || return the operand that decides. | 0 || 5 is 5 |
Unknown names, missing operands, and cycles are 0. | foo is 0 |
Division by zero gives Infinity. | 1 / 0 |
JavaScript math names work, plus gcd(a, b) and the GLSL math
mod, fract, clamp, mix, step, smoothstep,
radians and degrees, which read as in GLSL: mod(-1, 4) is 3.
Chained calls run right to left: tan.cos.sin(x).
match(test, value, test, value, …, else) gives the value after the first test that holds,
else the last odd argument or 0.
pick(k: a, b, …) gives item k from 1, wrapping around: pick(4: a, b, c) is a.
Both evaluate only the value they return.
Operators
Highest precedence first:
| Operators | Meaning |
|---|---|
! | not |
^ ** | power |
2x | values next to each other multiply |
- + before a value | sign |
* / ÷ % | multiply, divide, remainder (with the sign of the left side) |
+ - | add, subtract |
<< >> | shift |
& | bitwise and |
| | bitwise or |
< > <= >= = == != ≤ ≥ ≠ | compare |
&& ∧ | and |
|| ∨ | or |
Live values
@t, @ux,
@uw and the other values that follow time, the pointer or
the doodle's size are CSS variables, so they have no number when the doodle is generated.
The math around them is written out as CSS math instead:
$(@t / 10)deg becomes calc((var(--cssd-utime) / 10) * 1deg).
Arithmetic, comparisons, logic, match(), pick(k: …), the GLSL math and the common Math functions carry over.
Shifts, bitwise operators, gcd() and the other Math functions have no CSS form, so with a live value they give 0.
What is decided when the doodle is generated cannot wait for a live value.
@match, @cell and the @match function read it as 0,
and elsewhere it is dropped, so @r(@t, 5) is @r(5).
Cell variables
Cell selectors, @match, the index of @p and $() read these names as well as custom properties.
When a custom property has the same name, $() reads the property and the others read the cell variable.
| Name | Value |
|---|---|
x, y | column and row, starting at 1 in the top-left |
X, Y | how many columns and rows the grid has |
z, Z | depth level and number of levels, in a grid such as 1x1x8 |
i, I | this cell’s number (from 1), and the total number of cells |
dx, dy | offset from the center, in columns and rows |
dr, dc, dm | distance from the center: straight-line, square, and city-block |
da, db | angle from the center, in radians, and distance to the edge |
random | a random number between 0 and 1 |
Each ring’s size and hue come from $(…) and the cell index @i.
n is the custom property --n.
@grid: 12x1 / 60vmin;
--n: 12;
@place: center;
@size: $%(100 - 6 * @i);
border: 2px solid hsl($(360 / n * @i), 70%, 55%);
border-radius: 50%; Embedded languages
These functions take a body of their own, then compile it to an image or a shape.
You use the result as a CSS value, usually on background,
clip-path, or @content.
@doodle-
A nested doodle, rendered to an image, or inserted as an element with
@content.The body is ordinary doodle code. Digits on the name set the pixel size of the image.
background: @doodle( ... ); @svg-
An SVG image for
background,mask, or@content.Blocks are elements, declarations are attributes.
circle*8repeats the tag.background: @svg( ... ); @shape-
A
polygon()forclip-path, or ashape()when corners are rounded.A preset name, or commands that draw a curve in the angle
t(polarr, orxandy), place points, or join shapes in groups.clip-path: @shape( ... ); @pattern-
A pixel pattern drawn on the GPU, when a real grid would be too heavy.
A virtual grid, a
fill, andmatchrules that pick a color.@content: @pattern( ... ); @scene-
A 3D model drawn on the GPU, for
backgroundor@content.Shapes such as
sphereandbox, in the language of@shape.background: @scene( ... ); @shaders-
A WebGL image from GLSL, for
backgroundor@content.Fragment source, or named sections.
texture { }can hold a nested doodle.background: @shaders( ... );
The body still uses name: value and name { }.
Each language decides what a name and a value mean.
@grid: 1 / 60vmin;
background: @svg(
viewBox: 0 0 18 16;
circle*8 {
fill: #000;
r: .7;
cx: @n(*2);
cy: 8;
}
); Error recovery
Incomplete input still produces a doodle. css-doodle reports the problems below once per component, in the console and on the diagnostics property.
| What you wrote | What happens |
|---|---|
| An unknown property or at-rule | Emitted as written, without a warning |
| An unknown function with arguments | Emitted as its name, @name |
An unclosed ( | The value swallows the declarations that follow |
A @use variable that includes itself | Skipped the second time |
| A function that calls itself | Reported, and the call expands to nothing |
| A function parameter that is not a name | Reported, and the parameter is never replaced |