4.2Rule
4.3Nesting
4.6Value
7.1.1Changed
7.2.1Added
8.18
top
← prev up next →

CSS-expressionsπŸ”— i

Leandro Facchinetti <me@leafac.com>

(require css-expr ) package: css-expr

S-expression-based CSS.

Version

0.0.2

Documentation

Racket Documentation

Code of Conduct

Contributor Covenant v1.4.0

Distribution

Racket Package

Source

GitHub

Bug Reports

GitHub Issues

Contributions

GitHub Pull Requests

1OverviewπŸ”— i

S-expressions are a convenient way of representing hierarchical data. Racket’s syntax is based on S-expressions and it includes particular dialects to support embedded domain-specific languages. For example, one can use X-expressions to represent XML in general or HTML in specific. Embedding documents in Racket programs as X-expressions has several benefits: parts of the document can be generated programmatically, the text editor can syntax-highlight and indent the code without extra effort and transformations on the document are data structure manipulations which do not require custom tools.

CSS-expressions go great with Pollen and Pollen Component.

CSS is a domain-specific language for styling documents. Documents can be generated with X-expressions, so it is natural to want a S-expression-based representation for CSS as well. We introduce CSS-expressions, a domain-specific language embedded in Racket which is based on S-expressions and outputs CSS. In addition to the benefits of embedded domain-specific languages already mentioned, CSS-expressions also supports extended features generally found in CSS preprocessors including Sass, Less and Stylus. For example, nested rules and declarations. Finally, because CSS-expressions are embedded in Racket, many features from the CSS preprocessors are free: variables, mixins, operations, and so on.

Examples:
> (require racket/formatcss-expr )
> (~a
;Styles from http://bettermotherfuckingwebsite.com/
[body
#:margin(40pxauto)
#:max-width650px
#:line-height1.6
#:font-size18px
#:color|#444|
#:padding(010px)]
[h1h2h3
#:line-height1.2]))
069)"...")

"body{margin:40px auto;max-width:650px;line-height:1.6;font-size:18px;..."

2InstallationπŸ”— i

CSS-expressions are a Racket package. Install it in DrRacket or with the following command line:

$ raco pkg install css-expr

3UsageπŸ”— i

See CSS-expressions in action on my personal website (source). It also uses Pollen Component.

First, require the library. In Racket, add the following line:

(require css-expr )

Or, in Typed Racket, add the following line:

(require css-expr/typed)

Then, build CSS-expressions with css-expr . Finally, use css-expr->css to transform CSS-expressions into CSS.

syntax

( css-expr expr...)

Creates an CSS-expression from the given expressions. Escape to Racket code with unquoting (,expr). css-expr works like quasiquoting and only exists to clarify the intent of creating CSS-expressions.

Examples:
> (define main-width(css-expr 700px))
[body#:width,@main-width]
[.menu#:width,@main-width])

'((body #:width 700px) (.menu #:width 700px))

procedure

( css-expr->css css-expr)String

css-expr:Sexp
Compiles a CSS-expression stylesheet to CSS.

4LanguageπŸ”— i

The following subsections describe CSS-expressions with English and examples. A formal grammar is also available in the next section.

4.1StylesheetπŸ”— i

A complete CSS-expression is, at the top level, a stylesheet, which is a list of rules.

Examples:
[body
#:margin(40pxauto)]
[h1h2h3
#:line-height1.2]))

"body{margin:40px auto;}h1,h2,h3{line-height:1.2;}"

In the example above, ([body... ][h1h2h3... ]) is a stylesheet and each of [body... ] and [h1h2h3... ] are rules.

4.2RuleπŸ”— i

There are two kinds of rules: qualified rules and at-rules. Qualified rules contain declarations for the styles in the document.

Examples:
[body
#:margin(40pxauto)
#:font-family"Fira Sans"]))

"body{margin:40px auto;font-family:\"Fira Sans\";}"

In the example above, [body... ] is a qualified rule and each of #:margin(40pxauto) and #:font-family"Fira Sans" are declarations.

At-rules contain elements that control the stylesheet itself.

Identifiers starting with @ can be troublesome to generate programmatically (for example, (string->symbol (~a "@"some-computation))). So [@name... ] is an at-rule equivalent to [@name... ].

Examples:
[@import"other-stylesheet.css"]))

"@import \"other-stylesheet.css\";"

[@font-face#:font-family"Fira Sans"#:src"..."]))

"@font-face{font-family:\"Fira Sans\";src:\"...\";}"

[@media(and screen(#:min-width700px))
[body#:font-size20px]]))

"@media screen and (min-width:700px){body{font-size:20px;}}"

In the example above, [@import... ], [@font-face... ] and [@media... ] are at-rules. They differentiate from qualified rules by the @ prefix. The rule [@import... ] is requesting the browser to load another stylesheet; it shows how at-rules handle expressions (in the example, the expression is "other-stylesheet.css"). The rule [@font-face... ] is defining a new font and showing how at-rules can include declarations (in the example, #:font-family"Fira Sans" and #:src"..." are declarations). Finally, the rule [@media... ] is declaring qualified rules that only apply under certain conditions; showing that at-rules can include qualified rules (in the example, [body... ]) in addition to expressions (in the example, (and ... )).

4.3NestingπŸ”— i

CSS-expressions allow for arbitrary nesting of rules within one another, which is an extension to plain CSS generally found in preprocessors including Sass, Less and Stylus. The nested rules are unnested during the process of translating CSS-expressions into CSS.

Examples:
[.menu#:width700px
[.item#:text-decorationnone]]))

".menu{width:700px;}.menu .item{text-decoration:none;}"

[.menu#:width700px
[@media(#:min-width500px)
#:colorgreen]]))

".menu{width:700px;}@media (min-width:500px){.menu{color:green;}}"

[@mediascreen
[@media(#:min-width700px)
[body#:font-size20px]]]))

"@media screen and (min-width:700px){body{font-size:20px;}}"

In the first example, nested qualified rules illustrate how to compactly define components. In the second example, a media query is nested within a qualified rule, which is convenient for responsive design. The third example shows how nested at-rules compose.

When nesting qualified rules, the default combinator for selectors is the descendant combinator, which in CSS is pronounced with a space—see example above. To combine selectors differently, it is necessary to explicitly refer to the selector of the parent qualified rule in the selector of the nested qualified rule. Accomplish that using &.

Examples:
[.menu#:width700px
[(> &.item)#:text-decorationnone]]))

".menu{width:700px;}.menu>.item{text-decoration:none;}"

In the example above, the declaration #:text-decorationnone is only effective on .items that are immediate children of .menus.

The following kinds of parent selectors allow for an added suffix with &-: identifiers, namespaced selectors, prefixed selectors or combinations in which the last selector is one of the previous kinds.

Under special conditions one can declare nested rules that add a suffix to the parent selector. Accomplish that using &-.

Examples:
[.menu#:width700px
[(&-item)#:text-decorationnone]]))

".menu{width:700px;}.menu-item{text-decoration:none;}"

[|#main|#:colorblue
[(&-sidebar)#:colorpink]]))

"#main{color:blue;}#main-sidebar{color:pink;}"

4.4At-Rule ExpressionπŸ”— i

At-rule expressions occur in at-rules, after the @name and before any declarations or inner rules. In its simplest form, an at-rule expression is a value.

Examples:
[@import"other-stylesheet.css"]))

"@import \"other-stylesheet.css\";"

In the example above, "other-stylesheet.css" is a value standing for an at-rule expression.

Multiple at-rule expressions in a list translate as a composite expression in CSS.

Examples:
[@import("other-stylesheet.css"screen)]))

"@import \"other-stylesheet.css\" screen;"

In the example above, the list ("other-stylesheet.css"screen) translated to the composite expression "other-stylesheet.css"screen in CSS.

Multiple at-rule expressions in a row translate to alternative expressions in CSS.

Examples:
[@mediascreenprint
[body#:line-height1.2]]))

"@media screen,print{body{line-height:1.2;}}"

In the example above, each of screen and print are at-rule expressions on their own. They translate to alternative expressions in CSS—separated by the comma.

Declarations can be at-rule expressions, and they have to be surrounded by parenthesis to be distinguished from declarations in the body of the at-rule.

Examples:
[@media(#:min-width700px)
[body#:line-height1.2]]))

"@media (min-width:700px){body{line-height:1.2;}}"

[@font-face#:font-family"Fira Sans"#:src"..."]))

"@font-face{font-family:\"Fira Sans\";src:\"...\";}"

In the first example, (#:min-width700px) is a declaration working as an at-rule expression. Note the surrounding parenthesis; they are necessary to differentiate from declarations in the at-rule body, the case which the second example illustrates.

The last type of at-rule expressions is operations involving other at-rule expressions.

Examples:
[@media(and screen(#:min-width700px))
[body#:line-height1.2]]))

"@media screen and (min-width:700px){body{line-height:1.2;}}"

[@media(or screenprint )
[body#:line-height1.2]]))

"@media screen or print{body{line-height:1.2;}}"

[@media(not (and screen(#:min-width700px)))
[body#:line-height1.2]]))

"@media not screen and (min-width:700px){body{line-height:1.2;}}"

[@media(only screen)
[body#:line-height1.2]]))

"@media only screen{body{line-height:1.2;}}"

4.5DeclarationπŸ”— i

Declarations set CSS properties. In CSS-expressions, declarations are the property names as keywords followed by values and an optional !important flag.

Examples:
[body
#:line-height1.2
#:background-colorblack]))

"body{line-height:1.2;background-color:black;}"

[body
#:line-height1.2!important]))

"body{line-height:1.2 !important;}"

In the first example above, #:line-height1.2 and #:background-colorblack are declarations. The properties names are #:line-height and #:background-color, while 1.2 and black are values. The second example above illustrates the use of the !important flag.

CSS-expressions also support an extension generally found in CSS preprocessors including Sass, Less and Stylus: nested declarations.

Examples:
[body
#:font
(#:size18px
#:familyHelvetica)]))

"body{font-size:18px;font-family:Helvetica;}"

In the example above, the #:font common prefix has been factored out from the declarations of #:font-size and #:font-family.

Note that values can occur before the nested declarations.

Examples:
[body
#:fontitalic
(#:size18px
#:familyHelvetica)]))

"body{font:italic;font-size:18px;font-family:Helvetica;}"

In the example above, italic is a value before the nested declarations (#:size... ).

4.6ValueπŸ”— i

This simplest kinds of value are symbols, numbers and strings.

Examples:
[body
#:font-styleitalic
#:line-height3.5
#:font-family"Fira Sans"]))

"body{font-style:italic;line-height:3.5;font-family:\"Fira Sans\";}"

In the example above, italic is a symbol value, 3.5 is a number value and "Fira Sans" is a string value. Note how strings are quoted in the CSS output while symbols are not.

Similar to what happens in at-rule expressions, composite values must be enclosed in parenthesis.

Examples:
[body
#:font(italic18px"Fira Sans")]))

"body{font:italic 18px \"Fira Sans\";}"

In the example above, (italic18px"Fira Sans") is a composite value.

A list of values is just laid out after the property name, without enclosing parenthesis.

Examples:
[body
#:font-family"Fira Sans"sans-serif]))

"body{font-family:\"Fira Sans\",sans-serif;}"

In the example above, "Fira Sans" and sans-serif compose a list of values.

Writing colors as "#ff00dd" does not work, because the CSS would include quotes around the value.

Hexadecimal colors in CSS-expressions are just symbols, but it is necessary to escape the hash because of Racket’s parsing rules.

Examples:
[body
#:color|#ff00dd|]))

"body{color:#ff00dd;}"

In the example above, one can write the color as either |#ff00dd| or \#ff00dd.

Measurement symbols can be troublesome to generate programmatically (for example, (string->symbol (~a some-computation"px"))). So (px12) is a value equivalent to 12px.

Measurements are also just symbols.

Examples:
[body
#:margin-left12px]))

"body{margin-left:12px;}"

In the example above, 12px is a measurement. Valid measurement units are: %, em, ex, ch, rem, vw, vh, vmin, vmax, cm, mm, q, in, pt, pc, px, deg, grad, rad, turn, s, ms, hz, khz, dpi, dpcm and dppx.

Note that apply is a form in CSS-expressions. It results in CSS that looks like function application, which is not equivalent to unquoting from the CSS-expression and writing a Racket expression using Racket’s apply . For example, (apply calc2px) translates to calc(2px), while (px,(apply + '(11))) translates to 2px. Both are valid forms, useful in different contexts.

Use apply to write values that look like function application.

Examples:
[body
#:color(apply rgb203040)]))

"body{color:rgb(20,30,40);}"

In the example above, (apply rgb203040) is a value that looks like a function application in CSS.

Note that operations on values are forms in CSS-expressions. They result in operations in CSS, which are not equivalent to unquoting from the CSS-expression and writing Racket expressions. For example, (+ 2px3px) translates to 2px+ 3px, while (px,(+ 23)) translates to 5px. Both are valid forms, useful in different contexts.

Operations on values are also valid values.

Examples:
[body
#:width(apply calc(- 12px2px))]))

"body{width:calc(12px - 2px);}"

In the example above, (- 12px2px) is an operation. Valid operands are + , - , * and / .

4.7SelectorπŸ”— i

The simplest kind of selector is an identifier.

Examples:
[li#:width700px]))

"li{width:700px;}"

In the example above, li is a selector that matches all occurrences of the <li> HTML tag.

Multiple selectors in a row turn into a list.

Examples:
[lia#:width700px]))

"li,a{width:700px;}"

In the example above, the rule matches any <li> and any <a> HTML tag.

Enclose selectors in parenthesis to combine them with the descendant combinator—which in CSS is pronounced with a space.

Examples:
[(lia)#:width700px]))

"li a{width:700px;}"

In the example above, the rule matches any link (<a>) which occurs anywhere within a list item (<li>).

Namespaced symbols can be troublesome to generate programmatically (for example, (string->symbol (~a some-computation"|"other-computation))). So (\|nsa) is a selector equivalent to ns\|a. Again, note how it is necessary to escape the pipe (|).

Selectors can occur under a namespace.

Examples:
[ns\|a#:width700px]))

"ns|a{width:700px;}"

In the example above, the rule only matches <a> tags under the ns namespace. Note that the pipe (|) needs escaping because of Racket’s parsing rules for identifiers.

Prefixed symbols can be troublesome to generate programmatically (for example, (string->symbol (~a some-computation"#"other-computation))). So (|#|main) is a selector equivalent to |#main|, and (|#|divmain) is a selector equivalent to div#main. Again, note how it is necessary to escape the hash (#).

This applies to all prefixed selectors. For example, (|.|amenu) is equivalent to a.menu. Note that the dot alone (.) has special meaning in Racket, so it also requires escaping.

Selectors may require prefixes.

Examples:
[|#menu|#:width700px]))

"#menu{width:700px;}"

[.menu-item#:width700px]))

".menu-item{width:700px;}"

[a:hover#:width700px]))

"a:hover{width:700px;}"

[a::before#:width700px]))

"a::before{width:700px;}"

In the first example above, note that the hash—the prefix for selecting by id—needs escaping because of Racket’s parsing rules for identifiers. The alternative spelling \#menu works the same.

Note that apply is a form in CSS-expressions. It results in CSS that looks like function application, which is not equivalent to unquoting from the CSS-expression and writing a Racket expression using Racket’s apply . For example, (apply nth-child2) translates to nth-child(2), while (|.|,(apply string-downcase '(ODD))) translates to .odd. Both are valid forms, useful in different contexts.

Pseudo-classes that look like function applications use the apply form.

Examples:
[(:a(apply nth-child2))#:width700px]))

"a:nth-child(2){width:700px;}"

In the example above, (apply nth-child2) translates to a pseudo-class that looks like function application: nth-child(2).

The arguments to those pseudo-classes that look like function applications can be selectors, other nested pseudo-classes that look like function application and An+B forms.

Examples:
[(:a(apply not .classy))#:width700px]))

"a:not(.classy){width:700px;}"

[(:a(apply nth-childodd))#:width700px]))

"a:nth-child(odd){width:700px;}"

[(:a(apply nth-childeven))#:width700px]))

"a:nth-child(even){width:700px;}"

[(:a(apply nth-child3))#:width700px]))

"a:nth-child(3){width:700px;}"

[(:a(apply nth-childn))#:width700px]))

"a:nth-child(n){width:700px;}"

[(:a(apply nth-child(n2)))#:width700px]))

"a:nth-child(2n){width:700px;}"

[(:a(apply nth-child(n21)))#:width700px]))

"a:nth-child(2n+1){width:700px;}"

[(:a(apply nth-child(n+2)))#:width700px]))

"a:nth-child(n+2){width:700px;}"

In the first example above, (apply not .classy) is a pseudo-class that looks like function application whose argument another selector (.classy). The rest of the examples illustrate the An+B form.

See Nesting for the specific cases in which it is valid to add a suffix to the parent selector with &-.

In nested qualified rules, the selector & stands for the parent selector. The selector &- is a reference to the parent selector that allows for suffixes in limited cases.

Examples:
[.menu#:width700px
[(> &.item)#:text-decorationnone]]))

".menu{width:700px;}.menu>.item{text-decoration:none;}"

[.menu#:width700px
[(&-item)#:text-decorationnone]]))

".menu{width:700px;}.menu-item{text-decoration:none;}"

[|#main|#:colorblue
[(&-sidebar)#:colorpink]]))

"#main{color:blue;}#main-sidebar{color:pink;}"

Note that the || combinator requires escaping because of Racket’s parsing rules.

One can combine selectors in various ways.

Examples:
[(+ .menu.item)#:text-decorationnone]))

".menu+.item{text-decoration:none;}"

[(> .menu.item)#:text-decorationnone]))

".menu>.item{text-decoration:none;}"

[(~.menu.item)#:text-decorationnone]))

".menu~.item{text-decoration:none;}"

[((//for ).menu.item)#:text-decorationnone]))

".menu /for/ .item{text-decoration:none;}"

[(\|\|.menu.item)#:text-decorationnone]))

".menu||.item{text-decoration:none;}"

Attribute-based selectors use the attribute form.

Examples:
[(attribute.menuselected)#:text-decorationnone]))

".menu[selected]{text-decoration:none;}"

[(attribute.menu(= href"..."))#:text-decorationnone]))

".menu[href=\"...\"]{text-decoration:none;}"

[(attribute.menu(~=href(case-insensitive"...")))
#:text-decorationnone]))

".menu[href~=\"...\" i]{text-decoration:none;}"

Valid operands for attributes are: = , ~=, ^=, $=, *= and \|=.

5GrammarπŸ”— i

stylesheet = (rule...)
rule = [selector...+block-element...+]
| [@nameat-rule/expression...block-element...]
block-element = declaration
| rule
at-rule/expression = value
| declaration
| (at-rule/expression...+)
| (at-rule/expression/operation/operator/unaryat-rule/expression)
| (at-rule/expression/operation/operator/n-aryat-rule/expression...+)
at-rule/expression/operation/operator/unary = not
| only
at-rule/expression/operation/operator/n-ary = and
| or
declaration = #:namevalue...!important?(declaration...+)?
value = identifier
| number
| string
| (value/measurement/unitnumber)
| (value/operation/operatorvalue...+)
| (apply namevalue...+)
| (value...+)
value/measurement/unit = %
| em
| ex
| ch
| rem
| vw
| vh
| vmin
| vmax
| cm
| mm
| q
| in
| pt
| pc
| px
| deg
| grad
| rad
| turn
| s
| ms
| hz
| khz
| dpi
| dpcm
| dppx
value/operation/operator = +
| -
| *
| /
selector = name
| (selector...+)
| selector/namespaced
| (selector/prefixed/prefixselector?selector/prefixed/subject)
| (attributeselector?selector/attribute/subject)
| (selector/combination/combinatorcombinand:selector...+)
| &
| (&-suffix)
selector/namespaced = (\|name?name)
selector/prefixed/prefix = |.|
| |#|
| :
| ::
selector/prefixed/subject = name
| selector/function/application
selector/function/application = (apply nameselector/function/argument...+)
selector/function/argument = selector
| selector/An+B
| selector/function/application
selector/An+B = odd
| even
| integer
| n
| (nintegerinteger?)
| (n+integer)
selector/attribute/subject = name
| selector/namespaced
|
(selector/attribute/operation/operator
selector/attribute/operation/operand
selector/attribute/operation/operand)
selector/attribute/operation/operator = =
| ~=
| ^=
| $=
| *=
| \|=
selector/attribute/operation/operand = name
| string
| (case-insensitivestring)
selector/combination/combinator = +
| >
| ~
| (//name)
| \|\|

6AcknowledgmentsπŸ”— i

Thank you Matthew Butterick for Pollen—which motivated the creation of CSS-expressions—and for the feedback given in private email conversations. Thank you Greg Trzeciak for the early feedback. Thank you all Racket developers. Thank you all users of this library.

7ChangelogπŸ”— i

This section documents all notable changes to CSS-expressions. It follows recommendations from Keep a CHANGELOG and uses Semantic Versioning. Each released version is a Git tag.

7.10.0.2 · 2017εΉ΄03月08ζ—₯πŸ”— i

7.1.1ChangedπŸ”— i

  • Re-implemed CSS-expression from scratch using Nanopass. The user interface is the same.

7.20.0.1 · 2017εΉ΄02月01ζ—₯πŸ”— i

7.2.1AddedπŸ”— i

  • Basic functionality.

8ReferencesπŸ”— i

  1. Understanding the CSS Specifications.

  2. CSS Snapshot 2015.

  3. CSS spec­i­fi­ca­tions.

  4. CSS Syntax Module Level 3.

  5. CSS Syntax Module Level 3 Editor’s Draft.

  6. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification.

  7. Cascading Style Sheets Level 2 Revision 2 (CSS 2.2) Specification.

  8. Selectors Level 3.

  9. Selectors Level 4.

  10. CSS Values and Units Module Level 3.

  11. Media Queries.

  12. Media Queries Level 4.

  13. CSS Color Module Level 3.

  14. CSS Color Module Level 4.

  15. CSS Fonts Module Level 3.

  16. Sass (Syntactically Awesome StyleSheets).

  17. Less.

  18. Stylus.

top
← prev up next →

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /