“We should contemplate how very, very far behind the web platform is in making it delightful to build the sorts of things that are work-a-day in native environments.” Alex Russell
The foundation of GSS is a modernized implementation of CCSS on which we build more exotic layout APIs can and are accomplished.
Constraints, the basics
In the following example, all paragraph tags are constrained to have line-height greater than 16px and less than 1/12th the size of the window:
The syntax for declaring a constraint is as follows:
p[line-height] >= 16;
pis the selector. This constraint will apply to every
ptag in the page.
is the property accessor syntax.
line-heightis the property for which a value will be calculated by GSS.
>=defines an inequality constraint.
16is the numerical value for the constraint in pixels (the default unit of measurement in GSS).
Constraints are two-way
Do not confuse an equality constraint with your everyday variable assignment. For example, in a vanilla programming paradigm:
x will be 100, and y will be 10. With constraint programming, equality constraints are two-way:
x and y have a constraint to be equal, so x and y will be 100.
Constraints are incrementally added to a solver, then the solver computes a feasible & optimal solution to all the constraints. Constraint programming focuses on intentions, not implementation. This makes constraints a perfect fit for empowering declarative languages like CSS.
Regular imperative programming approach focuses on the implementation instead, making programmer solve the problems.
Constraints May or may not hold
GSS will find the best solution that satisfies the defined constraints. It uses “soft” constraints, which means that it is preferred but not required that certain constraints be satisfied.
#cassowary elements are constrained to have width of 200px initially, and then the sum of those two is set to equal 350px. In this case, one of these
two constraints will not hold. Live example.
It is possible to influence the solution by prioritizing the constraints with strengths.
Stronger constraints completely overcome weaker ones - this is the phenomena of the Constraint Hierarchy. Strengths are declared in the same fashion of CSS’s
There are 4 levels of built-in strength:
!require is a special strength that guarantees the constraint will hold, otherwise everything breaks.
Pro Tip Use
!require carefully & sparingly.
!require’s quickly lead to systems where
all required constraints cannot be satisfied in which case the GSS engine will throw an error.
For example, the following GSS constraints will be unsolvable and thus throw an error:
We’re asking GSS to find a solution where the widths of the #gss and #cassowary element must be 200px and their sum must be 350px, which is impossible.
Generally speaking, later constraint declarations of the same strength are more powerful than earlier ones. There are exceptions to this behavior, as we’ll see in the following sections.
Cassowary, which is the constraints solving algorithm used by GSS, can only compute “Linear Arithmetic” constraints. Simple math operations like
/ are all supported, but the expressions must be linear (of the form
y = mx + b). Basically, you can do everything except division and multiplication of two constrained variables.
Selectors are queries over a tree of HTML elements. They determine which elements are affected by the constraint.
Please reference the GSS Selectors Guide for a complete list of selector and more useful information on how to use them. *
* Note that this guide is refering to rulesets which are covered next.
Rulesets allow multiple constraints to be defined over a single selector
Within a ruleset, constraints are defined on properties using the same syntax as CSS using colon
: followed by a equality sign.
Rulesets can be nested:
CSS properties can be used in GSS rulesets:
Properties are variables belonging to an element. All previous examples constrained CSS properties like
It is possible to constrain custom properties that are not recognized by CSS, but can be used in GSS constraints over known
When using a property known by CSS, the GSS computed value for that property will be assigned as an inline style in pixels to the element.
& combinator refers to current element matching the ruleset selector. It is implied by the use of
width: == property notation.
It is possible to constrain custom properties, that are not recognized by CSS, that can be used in constraints over known CSS properties.
This will automatically create a
custom-property for each element having a
.post class. The element’s
will be constrained to be three times its own
Because of the 2-way binding, changing either
line-height will affect the other property of the element.
Properties on special combinators like
^ may omit square brackets:
Properties are attached to a specific element:
- In rulesets, it’s an element that matches the selector.
- In the topmost level of a stylesheet, it’s a
<style>that defines the stylesheet.
- In a stylesheet element with the
scopedattribute, topmost properties are attached to the parent element of a stylesheet.
Variables prefixed with
$ combinator are considered global, and can be shared accross different stylesheets.
Properties in stylesheets
Properties defined at the top level of a stylesheet will be attached to the stylesheet element. Thus each stylesheet provides its own scope. Top level properties are not directly accessible from other stylesheets.
In this example,
#elm1 will have a width of 100px and
#elm2 will have a width of 200px.
varX are in different scopes and are therefore independent from each other.
Global variables prefixed with
$, are accessible accross stylesheets. In the previous example, using
varX makes constraints operate on the same variable, making the widths of two elements equal.
scoped attribute to the stylesheet attaches top most properties to the parent element of a stylesheet. If two
scoped stylesheets share the parent element, they will automatically share the topmost variables.
The same scoping logic applies to external GSS files.
Properties in rulesets
Variables defined inside a ruleset define a property on each element that matches the ruleset selector.
In this case, both rulesets define
varX property for matching elements. If there’s an element that matches both rulesets,
it will have both constraints applied over a single property. This may lead to a conflict specific to that element.
The latter constraint will win in case of conflict if an element has both classes from the start. Otherwise the class that was added last will overcome previous ones. Strengths can be used to define the importance of each constraint independent from order of changes. Live example.
Variables are properties that dont have explicit scope and can be accessed from inner scopes. This concept is known as lexical scoping. The scoping is determined by the source code, so it’s static and does not change during runtime.
We say that topmost scope that defines variable captures the variable, making variables in the inner scopes hoisted.
varX was defined as a property on the top level (e.g.
varX: == 100), the variable would be captured
.className level instead.
The order of appearance or square brackets around variables have no effect over hoisting:
Variables are properties in disguise. If a variable has a name of a CSS property, it will apply the style for its resolved scope element. This can be used to define declarative constraints based on the DOM structure.
width variable in the
.wrapper ruleset captures the
width variable within
section ruleset. Therefore an element having
wrapper class name will have its width equal to height of its descendant sections, if there are any.
Rulesets can be nested arbitrarily deep, and the hoisting reference is computed for each variable individually. A variable that was not defined in any of its parent scopes, is considered local and is identical to property.
In this case, all nested rulesets will share the
varX variable through the parent ruleset. Nested rulesets also define
that are not hoisted and act like local properties. Live example.
Only variables capture other variables in nested rulesets. Anything that has explicit scope is considered a property. Properties do
not capture and are not hoisted. Examples would be
$global variables and
property: == notation is the cleanest
way to define property in a ruleset:
It is possible to opt out of hoisting in the same way by prefixing
& to a variable name, making it a property.
Suggested variables are global so they should be accessed with the
$ combinator. Live example.
External variables are bound in one direction, so their values will not be affected by the solver. It is almost as if there were actual numbers in place of those variables. The difference is that these values can be updated dynamically without much effort on the solver side:
Position and dimension
Consult our layout guide to learn more about positioning and dimensioning elements. Having a good understanding of layout constraints will help you appreciate the next section about virtuals.
Virtuals are rectangles defined in GSS that don’t have a representation in the DOM tree. However regular elements can be constrained against dimensions and positions of virtuals. It makes virtuals a practical way to constrain a group of elements without the need for a DOM container element.
In the example, both
#elmB elements are positioned against different corners of the virtual rectangle.
Since virtuals don’t exist in the DOM, applied styles have no visual effect:
Virtuals are in fact variables for rectangles. This is why virtuals follow the same scoping rules as variables. Hoisting logic applies to virtuals without explicit scope. Explicitly scoped virtuals are not hoisted and do not capture.
Virtual splats allow you to create a grid of virtuals. For example, if you want to split the viewport into four equally sized rectangles you can do the following:
Now any DOM element can be constrained against those four boxes. Live example.
GSS provides conditional statements for responsive design. Read our conditionals guide to learn more.
With only CCSS at your disposal, constraining common layout scenarios quickly becomes tedious. Read our VFL guide to learn how to more efficiently constrain your layout.