CSS Polyfills

Describe more using CSS!

Use CSS to describe menial tasks in JavaScript like wrapping elements for styling, moving content around, numbering things, clickable footnotes, and more!

Learn More GitHub

What if you could use CSS to do more?

// Use jQuery selector Extensions
.example:has(>.title) { border: 1px dotted; }
.example:lt(3)        { background-color: red; }

// Wrap Elements just for styling purposes
.example:outside        { border: 1px solid red; }
.example:outside:before { content: 'Example: '; }

// Floating, clickable Footnotes
.footnote:footnote-call { x-tag-name: 'a'; }

// Advanced Attribute Lookup
.note[title] > .title { content: x-parent(attr(title)); }

// Number links to other elements
a[href] {
  content: 'See Chap ' target-counter(attr(href), chapter)
                  ': ' target-text(attr(href), content());
}
// Customize Link text based on what the link target is
a[href] {
  content: x-target-is(attr(href), 'table') 'See Table';
  content: x-target-is(attr(href), '.note') 'See Note';
}
// Move content in the DOM
.definition    { move-to: glossary-bucket; }
.glossary-area { content: pending(glossary-bucket); }

// Set a string ... and then use it
h1 { string-set: chapter-title content(); }
.chap-end { content: '[End of ' string(chapter-title) ']'; }
run unit tests/coverage

Motivation

CSS is constantly changing and there is a lack of tools for experimenting with new ways of using it.

There are some great Working Drafts that describe extensions to CSS (Generated Content for Paged Media and CSS3 Generated and Replaced Content Module), features often implemented using ad-hoc JavaScript.

Below are some interactive examples showing how powerful CSS can be at describing these common operations.

For a full list of CSS selectors, rules, and functions see the GitHub README

Sections


How to Use in a Browser

Running in a browser is the easiest way to get started and good for testing out the CSS.

Include the stylesheet with the rel set to "stylesheet/css-polyfills":

<link rel="stylesheet/css-polyfills"
      type="text/css"
      href="styles.css" />

Then use the dcss-polyfills.js built in the /dist/ directory, and include it in your page, like so:

<script src="dist/css-polyfills.js"></script>

See examples/browser.html for an example.

Moving Content

Moving content in the DOM is simple using the move-to: bucket-name; and content: pending(bucket-name); rules defined in CSS3 Generated and Replaced Content Module.

// This element will be moved into the glossary-bucket...
.def-a { move-to: bucket-a; }
.def-b { move-to: bucket-b; }

// ... and dumped out into this area in the order added.
.area-a { content: pending(bucket-a); }
.area-b { content: pending(bucket-b); }

// Also, styling occurs **before** elements are moved so ...
.before div { background-color: lightgreen; }
<div class="before"> <div class="def-a">This will be in the 1st Area A</div> <div class="def-b">This will be in Area B</div> <div class="def-a">This will also be in the 1st Area A</div> </div> <h3>Area A</h3> <div class="area-a"></div> <h3>Area B</h3> <div class="area-b"></div> <div class="def-a">This will be in the 2nd Area A</div> <h3>Area A</h3> <div class="area-a"></div>

Looking up Cross-referenced text and counters

Looking up text inside some other element is simple using the content: target-text(attr(href)); and content: target-counter(attr(href), chapter-coutner); rules defined in CSS Generated Content for Paged Media Module.

// Just set a counter so we can look it up later
h3 { counter-increment: chap; }
h3:before { content: 'Ch ' counter(chap) ': '; }

// Look up the text on the target
.xref {
  content: 'See ' target-text(attr(href), content(contents));
}
// Look up the counter on the target
.xref-counter {
  content: 'See Chapter ' target-counter(attr(href), chap);
}
<h3 id="ch1">The Appendicular Skeleton</h3> <p>Here is a reference to another chapter: <a href="#ch2" class="xref">Link</a> </p> <h3 id="ch2">The Brain and Cranial Nerves</h3> <p>Here is a reference to another chapter: <a href="#ch1" class="xref">Link</a> </p> <p>A reference using target-counter: <a href="#ch1" class="xref-counter">Link</a> </p>

Making HTML elements in CSS

It may be useful to add elements into the HTML (for styling). CSS2 has the :before and :after selectors for this reason.

The CSS3 Content Module is like :before on steroids! You can nest the pseudo selectors to add and style arbitrarily large elements. It also introduces the :outside pseudoselector which wraps an element and adds additional styling.

The example below builds on the previous example of using counters and target-text to allow styling individual parts of the injected "Ch #: " text.

h3:before { content: 'Ch ' counter(chap) ': '; }
// The previous rule is valid CSS2 and creates the following DOM:
// [ [Ch 2:] Chapter Title]
// Note: There are only 2 elements we can style

h3                { counter-increment: chap; }
h3:before:before  { content: '[Ch ]'; }
h3:before         { content: '[' counter(chap) ']'; }
h3:before:after   { content: '[: ]'; }
h3:outside:before { content: '(chapter starts here)'; }

// Instead, the previous styles create the following
// (providing 7 elements that can be styled):
// [
//   [Chapter starts here]
//   [
//     [ [Ch] [2] [: ] ]
//     Chapter Title
//   ]
// ]
<h3 id="ch1">The Appendicular Skeleton</h3> <p>Lorem ipsum lorem ipsum.</p> <p>Lorem ipsum lorem ipsum.</p> <h3 id="ch2">The Brain and Cranial Nerves</h3> <p>Lorem ipsum lorem ipsum.</p> <p>Lorem ipsum lorem ipsum.</p>

Setting Strings

Sometimes it is useful to set a string and then use it later. An example would be having the current chapter on each page of a book.

Fortunately CSS Generated Content for Paged Media defines the string-set: string-name value; and content: string(string-name); rules.

An example is shown below:

// Set a string somewhere...
h3 { string-set: chapter-title content(); }
// ... And then use it!
.chap-end { content: '[End of ' string(chapter-title) ']'; }
<h3>The Appendicular Skeleton</h3> <p>Here is some content for the chapter.</p> <div class="chap-end"></div> <h3>The Brain and Cranial Nerves</h3> <p>Here is some content for another chapter.</p> <div class="chap-end"></div>

Sometimes it is useful to change the text of a link based on what it points to. For example, a link to a figure may say "see Figure 4.3" and a link to another chapter might say "See Section 7.2: Trapezoids".

Changing the text of a link can be accomplished using x-target-is(id, '.title') which works like a switch statement in many programming languages.

An example is shown below:

a[href] {
  // Use x-target-is as a switch for which link text to use
  content: x-target-is(attr(href), 'figure') 'See Figure';
  // Link to a section WITHOUT a title
  content: x-target-is(attr(href), 'section:not(:has(>.title))')
           'See ' target-text(attr(href), content(before));
  // Link to a section **with** a title
  content: x-target-is(attr(href), 'section:has(>.title)')
           'See ' target-text(attr(href), content(before))
           target-text(attr(href), content('> .title'));
}

// Some uninteresting formatting just for the demo
section { counter-increment: section; }
section::before { content: counter(section) ' '; }

<figure id="id-figure"><i class="fa fa-smile-o"></i> image</figure> <a href="#id-figure">LINK</a> <hr/> <section id="id-section">Section without a title</section> <section id="id-section-title"> <strong class="title">Kinematics</strong> Section with a title </section> <a href="#id-section">LINK</a> <br/> <a href="#id-section-title">LINK</a>

Putting it Together: Footnotes (Clickable and Floating)

Footnotes that move to the bottom of a page and leave a little numbered link are described in Generated Content for Paged Media: Footnotes

They can be implemented using the pseudoselector :footnote-call and the extension rules x-tag-name:, x-attr:, and x-ensure-id:.

Here is an example implementation of footnotes.

.footnote {
  // Ensure the footnote has an `id` (so we can link to it)
  x-ensure-id: 'id';
  // Move it to the next `footnote-area` (page end)
  move-to: footnote-area;
  counter-increment: footnote;
  display: block; // Plain CSS, nothing interesting
}
// The content that is left behind after the move-to
.footnote:footnote-call {
  // Make the stub that is left behind a link...
  x-tag-name: 'a';
  // ... whose href points to the footnote.
  x-attr: href '#' attr(id);
  content: '[###]';
  content: '[' target-counter(attr(href), footnote) ']';
}

//.footnote:footnote-marker,
.footnote:before {
  content: counter(footnote) ': ';
}

// Area where the footnotes will be collected
.footnotes-area {
  content: pending(footnote-area);
  //counter-reset: footnote;
}
<p>Text with a <span class="footnote">FOOTNOTE!</span>.</p> <p>More paragraphs with <span class="footnote">footnote text</span> <span class="footnote">another footnote</span>.</p> <hr/> <h3>Footnotes Area (bottom of the page/chapter)</h3> <div class="footnotes-area"></div>

Sizzle Selector Extensions

Sizzle has several useful extensions to CSS selectors that allow us to target elements better. Now, you can use them directly in CSS files!

Some examples include: :has() for looking inside an element and :lt() for matching elements less than a specific index.

Try using them with the demo below!

.example:has(>.title) {
  background-color: pink;
}

.count:lt(3) {
  background-color: lightblue;
}
<div class="example">Does not have title</div> <div class="example"><div class="title">Has title</div></div> <hr/> <div class="count">First</div> <div class="count">Second</div> <div class="count">Third</div> <div class="count">Fourth</div>

Adding Extensions

Continuing with the Move example, it may be nice to sort the elements being moved like in a glossary.

Adding these extensions is pretty straightforward. Here is the definition of x-sort(bucket).

'x-sort': (env, pendingElsNode) ->
  pendingEls = pendingElsNode.value
  sorted = pendingEls.sort (a, b) ->
    a = a.text()
    b = b.text()
    return -1 if (a < b)
    return 1

  return sorted

You can use it in a demo below. Note that the items are sorted alphabetically.

// This element will be moved into the glossary-bucket...
.def {
  move-to: glossary-bucket;
}

// ... and dumped out into this area in the order added.
.glossary-area {
  content: x-sort(pending(glossary-bucket));
}
<div class="def"> (C)ut hair </div> <div class="def"> (A)waken self </div> <div class="def"> (B)rush teeth </div> <h1>Glossary</h1> <div class="glossary-area"></div>