RenoirST is a DSL enabling programmatic cascading style sheet generation for Pharo developed by Gabriel Omar Cotelli.
RenoirST aims to improve CSS integration with existing web frameworks. To do that, RenoirST generates CSS out of Pharo code. Renoir features are: common properties declaration, CSS3 selector support, important rules, font face rules and media queries support. In this tutorial we will present the key features of RenoirSt with a large set of examples. This tutorial assumes some knowledge of CSS and Pharo. For a little introduction about CSS you can read the Seaside's book CSS chapter.
To load the library in your image, evaluate:
download a ready to use image from the Pharo contribution CI Server or install it using the Catalog/Configuration Browser.
The main entry point for the library is the class
CascadingStyleSheetBuilder. In a workspace or playground, inspect the result of the following expression:
You now have an inspector on your first (empty and useless) style sheet. Real stylesheets are composed of rules (or rule-sets), where each one has a selector and a declaration group. The selector determines if the rule applies to some element in the DOM, and the declaration group specifies the style to apply.
Our first useful style sheet will simply assign a margin to every div element in the DOM.
the expected result in CSS is:
declareRuleSetFor:with: is used to configure a rule-set in the builder. The message requires two blocks: the first block defines the selector
and the second defines the style to apply to elements matching the selector. The
selector argument of the first block is an entry point to construct the
selector (more on this later). The
style argument of the second block is an entry point to declare CSS properties and values.
The properties API is mostly defined following this rules:
We now present how the various CSS rules can be expressed with RenoirSt. RenoirSt supports many CSS types, comments, and even functional notation.
The library provides out-of-the-box support for the length, angle, time and frequency units in the CSS spec. There are extensions for
Float classes allowing to obtain lengths.
The supported length units are:
emrelative to font size
exrelative to "x" height
chrelative to width of the zero glyph in the element's font
remrelative to font size of root element
vw1% of viewport's width
vh1% of viewport's height
vmin1% of viewport's smaller dimension
vmax1% of viewport's larger dimension
pxpixels (note that CSS has some special definition for pixel)
The supported angle units are:
The supported time units are:
The supported frequency units are:
RenoirST also supports the creation of percentages:
50 percent is mapped to
50% in the resulting CSS.
Some properties require integer or floating point values. In these cases just use the standard Pharo integer and float support. For example:
The library supports abstractions for properties requiring color values. The shared pool
CssSVGColors provides easy access to colors in the SVG 1.0 list,
and the abstractions
CssHSLColor allow the creation of colors in the RGB and HSL spaces including alpha support.
In a real scenario you should avoid hard coding colors as in the examples. It is recommended to put colors in objects representing a theme or something that gives them a name related to your application.
RGB-Colors also support percentage values:
Notice the difference in the used message because there is no alpha channel specification.
A lot of values for CSS properties are just keyword constants. This support is provided by the classes
Some properties support a wide range of values. For example the
margin property can have 1, 2 , 3 or 4 values specified. If you only need one value, just
pass it as a parameter. For more than one value, use an array:
ZnUrl instances can be used as the value for properties requiring an URI. Both relative and absolute URLs are accepted. A relative URL is considered by default relative to the site root.
To use a URL relative to the style sheet, send to it the message
When declaring rule sets, the library supports attaching comments to them with the
RenoirST also supports defining stand-alone comments (not attached to any rule):
A functional notation is a type of CSS component value that can represent complex types or invoke special processing. Mathematical expressions, toggling between values, attribute references, and gradients are all supported in RenoirST.
The library provides support for math expressions using the
To let descendant elements cycle over a list of values instead of inheriting the same value, one can use the
attr() function is allowed as a component value in properties applied to an element or pseudo-element. The function returns the value of an attribute on
the element. If used on a pseudo-element, it returns the value of the attribute on the pseudo-element's originating element. This function is supported using
CssAttributeReference abstraction and can be used simply providing an attribute name:
RenoirST allows for providing the type or unit of the attribute (if no type or unit is specified the
string type is assumed):
Additionally, it is possible to provide a value to use when the attribute is absent:
A gradient is an image that smoothly fades from one color to another. Gradients are commonly used for subtle shading in background images, buttons, and many
other places. The gradient notations described in this section allow an author to specify such an image in a terse syntax. This notation is supported using
To represent a simple linear gradient from a color to another, send the
fading: message to
CssLinearGradient with the two colors in an array as a parameter:
The above code evaluates to:
By default, a gradient's direction is from top to bottom. To specify a different direction, the author can use the
to:fading: message instead:
The above code will result in a gradient with yellow on the left side and blue on the right side. The equivalent CSS is:
To specify a diagonal direction, an array must be passed as the
The above code will result in a gradient with blue in the top right corner and yellow in the bottom left one. The equivalent CSS is:
Directions can also be specified as an angle by sending the
The above code maps to:
Gradients can be fine-tuned by manipulating so-called color stops:
The above code maps to:
This results in a linear gradient from left to right with yellow at the left side and plain blue from 30% (of the horizontal line) to the right side. More than
two colors can be passed as argument to
fading:. This can be used to create rainbows:
This maps to:
To create radial gradients, the author must send messages to
CssRadialGradient. For example,
This results in a radial gradient with yellow at the center and green all around. Coordinates can be passed to both the first and second parameters of the
This maps to:
To make the gradient repeatable, just send to it the message
beRepeating. For Example:
Box Shadows are supported with
CssBoxShadow abstraction. This abstraction simplifies the use of the
Several shadows can be combined:
So far our focus was on the style part of the rule. Let's focus now on the available selectors. Remember that a CSS selector represents a structure used to match elements in the document tree. This chapter asume some familiarity with the CSS selectors and will not go in detail about the exact meaning of each one. For more details you can take a look at CSS3 selector documentation.
These selectors match a specific element type in the DOM. The library provides out-of-the-box support for HTML elements. One example is the
div selector used in the previous chapter:
The following other example matches
<ol> (ordered list) elements:
To get a list of the supported type selectors evaluate
CssSelector selectorsInProtocol: '*RenoirSt-HTML'.
Selectors can be combined to represent complex queries. One of the most common use cases is the descendant combinator:
This only matches if an
ol element is a descendant (direct or not) of a
The child combinator only matches when an element is a direct child of another one:
Siblings combinators can be created using
Class selectors can be created by sending
class: and id selectors can be created by sending
id:. For example,
You should not hardcode the classes and ids, they should be obtained from the same object that holds them for the HTML generation. You probably have some code setting the class(es) and/or id(s) to a particular HTML element.
A comma-separated list of selectors represents the union of all elements selected by each of the individual selectors in the list. For example, in CSS when several selectors share the same declarations, they may be grouped into a comma-separated list.
Attribute selectors are useful to match an element based on its attributes and their values.
The attribute presence selector matches an element having an attribute (without considering the value of the attribute):
The attribute value exact matching selectors matches an element having an attribute with a specific value:
The attribute value inclusion selector matches an element having an attribute with a value including as a word the matching term:
Other attribute selectors are used for substring matching:
The pseudo-class concept is introduced to allow selection based on information that lies outside of the document tree or that cannot be expressed using the simpler selectors. Most pseudo-classes are supported just by sending one of the following messages
Here is a small example that uses the pseudo-classes:
:lang(C) pseudo-class can be used by sending the message
The negation pseudo-class,
:not(X), is a functional notation taking a simple selector (excluding the negation pseudo-class itself) as an argument. It represents an element that is not represented by its argument. For more information take a look at the CSS spec.
This selector is supported sending the message
These selectors allow selection based on extra information that lies in the document tree but cannot be represented by other simpler selectors nor combinators.
:root pseudo-class represents an element that is the root of the document. To build this kind of selector just send the message
root to another selector:
:nth-child(an+b) pseudo-class notation represents an element that has
an+b-1 siblings before it in the document tree, for any positive integer or
zero value of
n, and has a parent element. For values of
b greater than zero, this effectively divides the element's children into groups of
a elements (the last group taking the remainder), and selecting the
bth element of each group. The
b values must be integers (positive, negative, or zero). The index of the first child of an element is 1.
In addition to this,
:nth-child() can take a number,
even as arguments. The value
odd is equivalent to
even is equivalent to
All structural pseudo-classes can be generated using the following messages:
|CSS pseudo-class||RenoirST selector message|
Pseudo-elements create abstractions about the document tree beyond those specified by the document language. For instance, document languages do not offer mechanisms to access the first letter or first line of an element's content. Pseudo-elements allow authors to refer to this otherwise inaccessible information. Pseudo-elements may also provide authors a way to refer to content that does not exist in the source document.
firstLine pseudo-element describes the contents of the first formatted line of an element.
firstLetter pseudo-element represents the first letter of an element, if it is not preceded by any other content (such as images or inline tables) on its line.
after pseudo-elements can be used to describe generated content before or after an element's content. The
content property, in conjunction with these pseudo-elements, specifies what is inserted.
CSS attempts to create a balance of power between author and user style sheets. By default, rules in an author's style sheet override those in a user's style sheet.
However, for balance, an
!important declaration takes precedence over a normal declaration. Both author and user style sheets may contain
declarations, and user
!important rules override author
!important rules. This CSS feature improves accessibility of documents by giving users with
special requirements control over presentation.
RenoirSt supports this feature through the
beImportantDuring: message sent to the style.
Note that the important properties must be created by sending the messages to the inner argument
importantStyle instead of the outer argument
@media rule specifies the target media types of a set of statements. The
@media construct allows style sheet rules that apply to various media in the
same style sheet. Style rules outside of
@media rules apply to all media types that the style sheet applies to. At-rules inside
@media are invalid in
The most basic media rule consists of specifying just a media type:
To use media queries in the library just send the message
declare:forMediaMatching: to the builder. The first block is evaluated with an instance of a
CascadingStyleSheetBuilder and the second one with a builder of media queries.
The media query builder will match any media type by default. To specify a media type just send it the message
type: with the corresponding media type. The
CssMediaQueryConstants provides easy access to the following media types:
The media query builder supports a variety of messages for additional conditions (called media features). Media features are used in expressions to describe requirements of the output device.
The following media feature messages are supported:
CssMeasurewith length units:
color:(the argument describes the number of bits per color component of the output device),
colorIndex:(the argument describes the number of entries in the color lookup table of the output device),
monochrome:(the argument describes the number of bits per pixel in a monochrome frame buffer),
grid:(the argument must be 1 or 0);
CssMeasurewith resolution units:
New units for resolutions are added using the
CssMeasure abstraction. This kind of measures can be created sending the messages
dpi (dots per inch),
dpcm (dots per centimeter) or
dppx (dots per pixel unit) to an integer or float.
Here is a final example to better understand the media features support:
The library doesn't provide out of the box support for non standard properties. Nevertheless, the message
vendorPropertyAt:put: is available to ease the creation of this kind of properties by the end user:
If you really want to use a vendor specific extension, It's better to create an extension method sending the
@font-face rule allows for linking to fonts that are automatically fetched and activated when needed. This allows authors to select a font that closely matches the design goals for a given page rather than limiting the font choice to a set of fonts available on a given platform. A set of font descriptors define the location of a font resource, either locally or externally, along with the style characteristics of an individual face.
This support is implemented in the builder:
This kind of rule allows for multiple
src definitions specifying the resources containing the data. This resources can be external (fonts fetched from a URL) or local (available in the user system). This kind of resources are supported using
Units package (available using the ConfigurationBrowser in Pharo) includes some extensions colliding with RenoirSt. RenoirST can automatically load a compatibility package if it's loaded after the
Units package. To test this integration there's a
specific continuous integration job, that loads
Units first and then
RenoirSt includes an optional group including some useful extensions. The Seaside framework includes its own class modeling URLs: when this group is loaded the instances of
WAUrl can be used in the properties requiring an URI:
This optional group also loads extensions to
CssDeclarationBlock so it can be used as a
JSObject in plugins requiring some style parameter or as the argument in a component
To load these extensions in an image with Seaside already loaded, you need to load the group
Development-Seaside-Extensions if you want the test cases (there is also a
stable-pharo-40 branch if needed):