AI websites that design themselves

Join the evolution

Become a founding member

Grid-Flavored VFL

Visual Format Language

With only inequality constraints at your disposal, constraining common layout scenarios quickly becomes tedious. To horizontally align 4 buttons within a panel with a gap of 10px requires something like:

#panel[left] + 10 == #button1[left];
#button1[right] + 10 == #button2[left];
#button2[right] + 10 == #button3[left];
#button4[right] + 10 == #button4[left];
#panel[right] - 10 == #button4[right];

Not to mention the obligatory size & vertical constraints needed to fully specify the panel and buttons - ugh...

This is where Grid-flavored VFL comes in. The brains at Apple came up with VFL as a more programatic way to install constraints in Cocoa AutoLayout. The idea is rather simple, the syntax should visually depict the layout. GSS takes the good parts of VFL and makes it more suitable for CSS, throws in some sugar and drops the cruft. So the above example of 4 buttons in a panel becomes:

@h |-(#button1)-(#button2)-(#button3)-(#button4)-| in(#panel) gap(10);

/* which is the same as */

@h |-10-(#button1)-10-(#button2)-10-(#button3)-10-(#button4)-10-| in(#panel);

/* which is the same, assuming we're using button tags, as */

@h |(button)...| in(#panel) gap(10);

Basics

Horizontal layouts can be created with the CSS directive @h.

Vertical layouts with the CSS directive @v.

An element is queried within parenthesis, ( Selector ), and a connection between elements is represented using a hyphen (or two hyphens separated by a number to represent the number of pixels apart the views should be). Similarly, a cushion between elements is made with a tilde.

Internally, GSS compiles VFL into CCSS (in)equality constraints, so after each VFL example the compiled CCSS is included.

WARNING: The examples below are under-constrained, they only constrain elements along one dimension of alignment, to ensure an element's size & position is fully-specified you will need to add more constraints.

Connections

Two elements can be horizontally connected without any gap. Live example.

FlushViews

/* VFL */
@h (#maroon)(#blue);

/* Equivalent CCSS */
#maroon[right] == #blue[left];

Connection Gaps

To align elements with a gap, use a hyphen and gap( Number | Constraint Variable | Element Property ):

StandardSpace

/* VFL */
@h (#button)-(#textField) gap(8);
/* or */
@h (#button)-8-(#textField);

/* Equivalent CCSS */
#button[right] + 8 == #textField[left];

If no gap() is defined, but a hyphen is used to make a connection then a standard gap variable is created.

/* VFL */
@h (#button)-(#textField);
@v (#button)-(#textField);

/* Equivalent CCSS */
#button[right] + hgap == #textField[left];
#button[bottom] + vgap == #textField[top];

The hgap & vgap variables can be used like normal variables and are declared in the current scope.

User defined variables can be used for gaps as well. Live example.

/* VFL */
@h (#box1)-(#box2) gap(col-gap);

/* Equivalent CCSS */
#box1[right] + col-gap == #box2[left];
/* VFL */
@h (#b1)-100-(#b2)-(#b3)-(#b4) gap(#box1[width]);

/* Equivalent CCSS */
#b1[right] + 100 == #b2[left];
#b2[right] + #box1[width] == #b3[left];
#b3[right] + #box1[width] == #b4[left];

Containment within an Element

To align the whole VFL declaration within a specific element, a combination of in(#selector) and vertical | pipes can be used.

The pipes determine the edges of the element. In @h VFL blocks leading and trailing pipes signify left and right edges of the containing element. In @v declarations it's top and bottom edges respectively. Live example.

Binding to both edges of the element is a safest bet, as it makes a two-way relation between element and its contents. The use case of binding to only one edges would be layouts growing in one direction without expanding the containing element, like scrollable content.

ConnectionToSuperview

/* VFL */
@h |-50-(#purple-box)-50-| in(#container);

/* Equivalent CCSS */
#container[left] + 50 == #purple-box[left];
#container[right] - 50 == #purple-box[right];

If there's no in(selector) provided, the edges will point to the current element within ruleset. Using in(&) would have the same effect.

The above example can be written:

/* VFL */
#container {
  @h |-50-(#purple-box)-50-|;
}

Outer-Gaps

The variable provided in gap() declaration is used for all gaps between elements in VFL declaration. The outer-gap() function can be used to set a different variable for gaps between the containing element and first and last contained elements.

/* VFL */
#container {
  @h |-(#a)-(#b)-(#c)-|
    gap(8) outer-gap(16);
}

/* Equivalent VFL */
@h |-16-(#a)-8-(#b)-8-(#c)-16-| in(#container);

Alignment to Points

Elements can be aligned relative to arbitrary positioned points of the document using angle bracket syntax. The points can be either numbers or variables. Live example.

To horizontally align two buttons, each 8px from the center of the window:

/* VFL */
@h (#btn1)-<::window[center-x]>-(#btn2) gap(8);

/* Equivalent CCSS */
#btn1[right] + 8 == ::window[center-x];
::window[center-x] + 8 == #btn2[left];

Elements can be positioned between points:

/* VFL */
@h <#wall[center-x]>-(#poster)-(#clock)-<::window[right]> gap(7);

/* Equivalent CCSS */
#wall[center-x] + 7 == #poster[left];
#poster[right] + 7 == #clock[left];
::window[right] - 7 == #clock[right];

Numbers, variables and arithmetic can be used:

/* VFL */
@v <100>(#box)<[row2]>;

/* Equivalent CCSS */
100 == #box[top];
#box[bottom] == [row2];

Consecutive points are not constrained to each-other:

/* VFL */
@h (#btn1)-<#col3[left]>
           <#col4[right]>-(#btn2)
  gap(8);

/* Equivalent CCSS */
#btn1[right] + 8 == #col3[left];
#col4[right] + 8 == #btn2[left];

Cushions

Cushions are expandable connection gaps. They use inequality instead of equality constraints. Live example.

/* VFL */
@h (#box1)~(#box2);

/* Equivalent CCSS */
#box1[right] <= #box2[left];
/* VFL */
@v (#box1)~100~(#box2);

/* Equivalent CCSS */
#box1[bottom] + 100 <= #box2[top];
/* VFL */
@h (#box1)~-~(#box2);
@v (#box3)~-~(#box4);

/* Equivalent CCSS */
#box1[right] + [hgap] <= #box2[left];
#box3[bottom] + [vgap] <= #box4[top];

Size Predicates

Size Predicates allow elements to be sized in the dimension parallel with the alignment. Live example.

Priority

/* VFL */
@h (#button(==100));

/* Equivalent CCSS */
#button[width] == 100;

EqualWidths

/* VFL */
@h (#button1(==#button2));
/* or */
@h (#button1(==#button2[width]));

/* Equivalent CCSS */
#button1[width] == #button2[width];

Multiple size predicates:

MultiplePredicates

/* VFL */
@h (#flexibleButton(>= 70, <= 100));

/* Equivalent CCSS */
#flexibleButton[width] >= 70;
#flexibleButton[width] <= 100;

VFL Selectors

VFL expressions can use any complex selectors:

@v | (.featured > article ~ .title) | in(#elm > .class .class2"virtual")

VFL Splat

When a selector in a VFL expression finds more than one element, not all elements will be constrained if the number of elements within each selector's expression are not equal. This is due to plural binding. Live example.

In this example, the last two article elements will not be constrained since there is no section element to bind to. As can be seen in the generated CCSS, the elements are constrained using plural binding.

<section></section>
<article></article>
<article></article>
<article></article>
<footer></footer>

<style type="text/gss">

  /* the last two articles will not be constraints because of plural binding */
  @h |-(section)-(article)-(footer)-| in(::window);

  /* this generates the following CCSS */
  ::window[left] + [hgap] == section[left];
  section[right] + [hgap] == article[left]; /* plural binding */
  article[right] + [hgap] == footer[left]; /* plural binding */
  footer[right] + [hgap] == ::window[right];

</style>

If the number of selected elements matches, the position of the elements within each collection of selected elements will overlap since you're basically constraining the whole collection to the same values. Live example.

<section></section>
<section></section>
<section></section>
<article></article>
<article></article>
<article></article>
<footer></footer>

<style type="text/gss">

/* the 3 sections will all have the same horizontal position as the 3 articles. */
@h |-(section)-(article)-(footer)-| in(::window);

</style>

Splats will iterate over the collection of selected elements and position elements next to each other. Live example.

<section></section>
<section></section>
<article></article>
<article></article>
<article></article>
<div></div>
<div></div>

<style type="text/gss">

/* not constrained using plural binding */
@h |-(section)-10-...-(article)-20-...-(div)...-| in(::window);

</style>

In the previous example, the two sections will be positioned next to each other with a gap of 10px. The three articles will be positioned next to each other with a gap of 20px. Finally the two divs will be right next to each other without any gap.

Virtual elements with VFL

Virtual elements can be created and/or positioned using VFL expressions.

You can position a virtual element the same as any other element:

/* horizontally align two virtuals with a gap of 10px between them */
@h |-("area1")-10-("area2")-| in(::window);

VFL rulesets

You can use a VFL expression as a selector in rulesets. It will create a collection of all elements positioned by the VFL declaration, not including the containing element. Properties defined in the body of the ruleset will be applied to each of those elements.

In the following example, each element width is bound to the same variable, making them equal width:

shared-width >= 100;

@h |-(section)-10-...-(article)-20-...-(div)...-| in(::window) {
  width: == shared-width;
}

VFL Strengths

Strength used in VFL will be applied to all constraints generated by the statement:

/* VFL */
@v (#topfield)-10-(#bottomfield) !strong;

/* Equivalent CCSS */
#topfiled[bottom] + 10 == #bottomfield[top] !strong;

Size Predicates may have their own strength:

/* VFL */
@v (#box1(>= [row-height] !require, == 20 !strong))(#box2)(#box3) !medium;

/* Equivalent CCSS */
#box1[height] == 20 !strong;
#box1[height] >= [row-height] !require;
#box1[bottom] == #box2[top] !medium;
#box2[bottom] == #box3[top] !medium;
privacy policy