[Here will be included the file "../copyright.inc"]
This module defines parse-time constants with poweful scoping.
This is an unapproved working draft and has no official status at the moment.
Comments on, and discussions of this draft can be sent on the (archived) public mailing list www-style@w3.org (see instructions).
This is a working draft and may therefore be updated, replaced or rendered obsolete by other documents at any time. It is inappropriate to use Working Drafts as reference material or to cite them as other than "work in progress". Its publication does not imply endorsement by the W3C membership or the CSS Working Group (members only).
To find the latest version of this working draft, please follow the "Latest version" link above.
Web authors have been requesting some form of constants or variables or macros for the past decade. These requests have always been met by the suggestion to use a preprocessor. However no such processor has become a serious solution for this problem.
Daniel Glazman (Disruptive Innovations) and Dave Hyatt (Apple Software) have put forward a proposal for CSS Variables to fill this gap. It is both complicated and limited by trying to keep the variables scriptable. The goal of this counter-proposal is to define a purely parse-time syntax for CSS macros, one that addresses several of the most common use cases without burdening the implementation.
Use cases include:
Implementation constraints include:
There are three types of named constants: value constants, style-set constants, and selector constants. A value constant represents a property value, and a style-set constant represents one or more property declarations. Similarly, a selector constant represents a selector. Constant names are case-sensitive, and each type of constant has its own name space (so a value constant name cannot conflict with a style-set constant name).
All three types of constant are declared with an @define
rule. The
syntax of an @define
rule is a the @define
at-keyword,
followed by either the values
keyword (for declaring value constants)
or the style-sets
keyword (for declaring style-set constants)
or the selectors
keyword (for declaring selector constants),
followed by a block.
@define values { ... } @define style-sets { ... } @define selectors { ... }
The contents of the @define
block depends on whether the block
is defining value constants, style-set constants, or selector constants.
The contents of an @define values
block looks very similar to a
declaration block
and has the same syntax: a semi-colon-separated list of declarations, each given
as an identifier followed by a colon followed by a value. In this case the
identifier is the author-defined name of the constant rather than a property name.
The value must conform to the
core grammar
for value
s; otherwise, as for property declarations, the declaration
is invalid and must be ignored.
@define values { textColor: black; bgColor: white; accentColor: silver; accentBorder: double silver 5px; }
The contents of an @define style-sets
block looks similar to a
style sheet consisting solely of style rules
where each selector is a tag name selector: the style-set constants are given
as author-defined identifiers, each followed by a declaration block.
The same parsing rules apply to these declaration blocks as apply to declaration
blocks in style rules.
@define style-sets { noteBox { border-style: solid; padding: 1em; border-radius: 1em; } quoteBox { border-style: solid none; padding: 1em; margin: 1em; } }
The contents of an @define selectors
block have the same syntax
as the @define values
syntax except a selector appears after the colon
in place of the value.
@define selectors { navli: .navigation > ul > li; nava: .navigation > ul > li > a; }
The selectors in a @define selectors
must be valid selectors, and
furthermore may not use the comma-separated
grouping syntax. Just
like the declarations in an @define style-sets
rule, a selector
constant declaration that is invalid according to this definition must be ignored.
@define selectors { datarow: table.data > tbody > tr; datacell: table.data > tbody > tr > td, table.data > tbody > tr > th; datagroup: table.data > tbody; }
In the above example, the datacell
declaration is invalid and
will be ignored, but the datarow
and datagroup
declarations are valid and will be preserved.
I'm aware it would be nice if the datacell
declaration were
valid, but since selector constants can be expanded in the middle of a
selector that would add functionality (or confusion) to selectors that is not
already there. Selectors Level 4 should add some syntax, such as a
:matches()
pseudo-class, to group alternates within a given
selector. With that, datacell
could then be declared as below:
@define selectors { datarow: table.data > tbody > tr; datacell: table.data > tbody > tr > :matches(td, th); datagroup: table.data > tbody; }
@define
rules may appear both before and after @import
rules.
Named constants are in-scope from the point at which they are declared until
overridden by a later declaration. If declared inside an @media
rule,
the scope of the declaration does not end with the @media
block.
The scope of a named constant does not ordinarily cross @import
boundaries. However this module extends the syntax of the @import
statement to allow an additional keyword between the @import
keyword and the style sheet URI, and a named constant's scope can cross
@import
boundaries if an appropriate keyword is present.
This allows implementations to easily process style sheets asyncronously when
there are no constants to import, and for the author to control the scope of
the constants. The keywords are:
In the following example, the pull
keyword is used to pull in
a standard color palette.
@import pull "corporate-colors.css";
In the following example, a site-wide style sheet is modified for each sub-section by modifying the colors only.
@define values { textColor: #003684; baseTheme: #0068FF; lightTheme: #8CBBFF paleTheme: #D2E4FF; accentColor: #FFBE00; } @import push "template.css";
Once declared, a named constant may be used to represent its value in an appropriate context. To do so, the constant's name must be prepended with a <insert your favorite punctuation character here> backquote (`). For example,
`datarow > td, `datarow > th { `notebox; background: url(gradient-corner.png) top left `accentColor; }
Style-set constants should be followed by a semicolon, otherwise the declaration after the constant will be ignored in down-level clients. However UAs supporting named constants must not require the semicolon to parse the rules as valid.
Processing of named constants happens at parse time, after tokenization.
The processing model for constants is that the UA maintains a table of
name—value mappings and adds or replaces definitions as it encounters
@define
blocks. The name—value tables for value constants,
style-set constants, and selector constants are independent. Upon encountering
a previously-declared constant reference as it parses the style sheet, the UA
replaces the constant with its value and continues by parsing the newly
expanded value in place. If a constant is used before being declared, then it
is not expanded, and the normal rules for ignoring invalid constructs apply.
Constant references inside @define
blocks are not
expanded when the @define
block is parsed, but instead are
expanded when the constant is expanded. This means that if a constant is
declared to map to another constant, and that other constant is defined (or
redefined) in a later @define
block before being used in a
style rule, then it will pick up the definition in that later @define
block.
@define values { accentColor: blue; } p { background: white; } @define style-sets { box { color: black; background: `altBG; border: solid `accentColor; } } .notebox { `box; } @define values { accentColor: orange; altBG: yellow; } .warningbox { `box; }
In the above example, a p.notebox
will have a blue border and
a white background, but a p.warningbox
will have an orange border
and a yellow background.
When handling imported style sheets, the UA handles the name—value
tables differently depending on the type of @import
statement
it encounters:
@import
statement (no synchronization
keyword), a new set of name—value mapping tables are used to
parse the imported style sheet.
sync
import, the same name—value
tables are used to parse both the importing style sheet and the
imported style sheet, and the imported style sheet must be parsed
at the point where it is imported.
pull
import, a new set of name—value tables
are created to parse the imported style sheet, but after the imported
style sheet is parsed and before parsing continues after the
@import
statement in the importing style sheet the two
sets of name—value tables are merged with the definitions from
the imported style sheet's tables overwriting the definitions in the
importing style sheet's tables.
push
import, the name—value tables are
copied from the importing style sheet to the importing style sheet,
so the imported style sheet gets the definitions from the importing
style sheet above the @import
, but constant declarations
in the imported style sheet do not affect the tables used to parse the
importing style sheet.
Note that this model means that if a style sheet is imported twice with
at least one import using either a sync
or push
keyword, the style sheet may need to be parsed twice to handle the constants
correctly.
Inspiration for this proposal comes from the many responses to the WASP's call for feedback on CSS3 and various other blog posts requesting 'variables", "macros", "constants", "directed inheritance", and all sorts of other less clearly-defined things. :)