The language
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.:doodle:container:hoverspan - Conditional blocks
-
Style only the cells that match.
@mediakeeps its CSS meaning.@keyframesis hoisted.@cell(…)@nth(…)@random
Blocks can nest. Comments are /* … */. As in CSS, the last declaration
before } does not need a semicolon.
@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.
Optional parentheses
Drop the parentheses when there are no arguments. @i means @i().
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) |
@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.
@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. ±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.
| 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 | A whole row or column @row(even) @col(1) |
@even, @odd | A checkerboard |
@random | Cells at random. A value below 1 is a probability. @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. Units are dropped. | --w: 10px reads as 10 |
Comparisons give 1 or 0. | x = y |
| Values next to each other multiply. | 2x, 2π, (1 + 2)(3) |
Custom properties drop the -- prefix. | --n: 12 is n |
Unknown names, missing operands, and cycles are 0. | foo is 0 |
JavaScript math names work, plus gcd(a, b) and match(cond, a, b).
Chained calls run right to left: tan.cos.sin(x).
Operators
Highest precedence first:
| Operators | Meaning |
|---|---|
! | not |
^ ** | power |
* / % | multiply, divide, remainder |
+ - | add, subtract |
<< >> | shift |
& | bitwise and |
| | bitwise or |
< > <= >= = == != ≤ ≥ ≠ | compare |
&& | and |
|| | or |
Cell variables
Inside a cell selector, these names are available as well as custom properties:
| Name | Value |
|---|---|
x, y | column and row, starting at 1 in the top-left |
X, Y | how many columns and rows the grid has |
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: 1x12 / 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.A preset name, or polar commands in the angle
t.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.background: @pattern( ... ); @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 each of these 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 |
| An unknown function with arguments | Emitted as its name, @name |
An unclosed ( | The value swallows the declarations that follow |