How to cite this paper

DeRose, Steven J. “Dynamic Style: Implementing Hypertext through Embedding Javascript in CSS.” Presented at Balisage: The Markup Conference 2018, Washington, DC, July 31 - August 3, 2018. In Proceedings of Balisage: The Markup Conference 2018. Balisage Series on Markup Technologies, vol. 21 (2018).

Balisage: The Markup Conference 2018
July 31 - August 3, 2018

Balisage Paper: Dynamic Style

Implementing Hypertext through Embedding Javascript in CSS

Steven J. DeRose


Steve DeRose has been working with electronic document and hypertext systems since joining the FRESS project in 1979. He holds degrees in Computer Science and Linguistics and a Ph.D. in Computational Linguistics from Brown University.

He co-founded Electronic Book Technologies in 1989 to build the first SGML browser and retrieval system, DynaText, and has been deeply involved in document standards including XML, TEI, XPath, XPointer, EAD, Open eBook, OSIS, HyTime, and others. He has served as Adjunct faculty in Computer Science at Brown University and Calvin College, and written many papers and patents, and two books. He works as a consultant in text systems and analytics.

Copyright ©2018 Steven J. DeRose. Creative Commons Attribution – No Derivatives.


I here present a tool, called AJICSS, intended to make two things easier: first, developers’ transition from CSS to Javascript; and second, implementation of hypertext capabilities that involve real-time modifications to documents. In short, AJICSS:

  • lets you embed Javascript directly inside CSS rules (multiple reasons for this are discussed), and trigger it on demand.

  • provides functions to facilitate useful things such as dynamic layout and content changes.

  • facilitates implementing several hypertext features, including structural transclusion, stretchtext, user-controllable content changes and filtering, and spreadsheet-like tables.

AJICSS is written in Javascript, but is much easier to use than Javascript alone, and in particular makes it fairly easy (even for those with little programming background) to accomplish several long-neglected hypertext capabilities.

Table of Contents

A sample task
The step to Javascript
jQuery to the (partial) rescue
The integration issue
AJICSS embedding
Dynamic control of layout
The analogy of spreadsheets
Content changes
Display keywords
CSS hiding
How could CSS integrate similar functionality?
AJICSS implementation


CSS [Bos] is ubiquitous and conceptually simple to learn. It build on the conceptual bases of HTML, XML, and basic typography, and consists largely (though certainly not entirely) of a long list of parameters one can set, plus a selector language for applying such settings to groups of elements. Perhaps the most complicated CSS notion is how rules are prioritized when various selectors all match an element in its particular context.

CSS involves some programming-like concepts such as trees, scope, and inheritance. These closely relate to HTML itself, so are reasonably easy for those using HTML already. Using nested lists or div elements (or thinking of h2 as subordinate to h1 even without div), already requires at least tacit knowledge of such concepts.

On the other hand, CSS lacks many other desiderata of full-fledged programming languages: flow of control, programmer-defined constructs such as functions, classes, and datatypes (and related concepts such as casting), generic and custom collections such as arrays, and most logic, math, string, and other operators. CSS properties can access very little document or external data.

In contrast, Javascript [EC] is a general programming language with the attendant features, even though it has a few special limitations such as lack of access to the local file system (when running in a browser).

This dichotomy suggests a utopian hope: that one could (perhaps from background only in HTML) first learn CSS (with its simpler models and notions), and then (as needed) move on the more capable Javascript. This would be simplest if CSS’s design space and boundaries were largely a subset of Javascript’s, so that what one learns in the first largely applied in the second. When needed one could add more concepts of full-fledged programming, building upon the concepts already known. The transition from quasi-programming in CSS to full-fledged programming in Javascript would be easy and smooth. In principle the learning curve need not be steep, particularly because both operate on the same underlying document concepts and structures.

Unfortunately, that small utopia is not reality. CSS and Javascript have quite different ways of doing the same things, and the area in which it is unclear whether to use one or the other has a complex, irregular shape and many debatable questions.

A sample task

Consider a conceptually simple task, to be done in a typical browser: Display a correct start-tag before, and end-tag after, each element of a document.

We will ignore the issue of generating exactly the same tag as occurred in the input, for example having the same whitespace within it, suppressing end-tags that were omitted, or worrying about the order of attributes. Let it suffice to show the tags as they would appear in the canonical XML [Boy] equivalent of the document. For example, XML source like:

<p>Hello, <i>cruel<i> world.</p>

should display with literal angle-brackets, tag names, etc. as well as the usual formatting.

This can be done in many ways, such as running global changes on the document to insert an escaped copy of each tag immediately after the original. To have the copies formatted specially (for example, colorized), those changes could also insert (non-escaped) tags around them. But modifying the document itself in this way is tedious and error-prone. Many would consider it bad practice. It might also require schema changes to remain valid.

A better solution is to treat showing the tags as a detail of rendering, much like inserting quotation marks at the boundaries of HTML <q> elements. Such quoting can be done with two simple CSS rules.[1]

q:before { content:'"'; }
q:after  { content:'"'; }

The same method can insert literals that look like tags, as shown below (to embed the CSS directly within HTML might need more escaping):

p:before { content:'<p>'; font-size:smaller; color:green; }
p:after  { content:'</p>'; font-size:smaller; color:green; }

Particular attributes can also be inserted, though to insert one only when it actually has a value takes an additional rule and more advanced CSS selector (see section 6.3 of CSS Selectors 3 [Çel]):

p[id]:before { content:'<p id="' attr(id) '">'; ... }
Also displaying name when it has been specified, doubles the number of :before rules by adding a version with name for each version already needed (and so on):
p[name]:before { content:'<p name="' attr(name) '">'; ... }
p[id][name]:before { content:'<p id="' attr(id) ' name="' attr(name) '">'; ... }

Doing all this in a single rule requires functionality to obtain the current element type name, and to test and conditionally include each attribute.[2] One of many potential ways to express that could be:

*:before {
    content:'<' tagName()
        if(attr(name)) { ' name="' attr(name) }
        if(attr(id)) { ' id="' attr(id) }
    ... }
Adding a tagName() function seems a minor tweak, but the last example seems to cross the line into real programming. That line is surely flexible, and perhaps vague, but an if construct along with the and, or, not, comparison, and grouping capabilities that almost inexorably follow it, surely breaks rather than merely bends it.

The step to Javascript

The typical response to such cases is to switch to a full-fledged programming language, which provides such concepts and mechanisms as a matter of course. Javascript gives full access to the HTML document structure (element type names, attributes, IDREF following, nesting depth, etc.), can manipulate strings and other data in practially unbounded ways, and can insert or modify text and/or nodes wherever desired. But crossing that line requires many new concepts and skills, some potentially difficult. Generally, one must do all of:

  1. shift from quasi-programming (e.g., no variables or flow of control) to real programming[3]

  2. take on new tasks such as getting the Javascript applied at the right times (in terms of events such as the page being ready) — this introduces a range of dynamic / time- and process-oriented notions. For example:

    window.onload = function WindowLoad(event) {

  3. fiddle with syntactic differences, such as different escaping in Javascript vs. CSS when each is embedded directly in HTML files.

  4. master a completely different way of thinking about how things happen. With CSS, properties are applied whenever the rules they are in do. With Javascript, some event (perhaps only startup) must trigger execution of code, which then searches for elements of interest and modifies them (see below). At that time, CSS selection has already happened, with no direct connection to what Javascript code is triggered. Javascript also has in play not only the style sheet(s), but the values that eventuated from applying them at each element, and any changes other Javascript might have made.

  5. master a completely different way to search and modify document structure (DOM expressions rather than CSS selectors):

    ul p[class=big] { font-size:24pt; }
    becomes something like
    function setBigParas() {
        var paras = document.getElementsByClassName('big');
        for (var i = 0; i < paras.length; i++) {
            if (paras[i].nodeName != 'P' ||
           paras[i].parentNode.nodeName != 'UL') continue;
            var cl = (" " + paras[i].className + " ")
               .replace(/[\n\t\r]/g, " ");
            if (cl.indexOf(" big ") > -1)
                = "24pt";

This seems an awfully big change in order to accomplish the very same thing.

jQuery to the (partial) rescue

jQuery [jQuery] is a popular library for Javascript with the admirable features of supporting CSS selectors to choose groups of elements and of acting on such groups without manually looping over their members. jQuery’s $() function takes a CSS selector and returns a list of the element objects that match it. As in many languages, o.p accesses a datum or method p of an object o. So with jQuery the previous example requires little more than (greatly) different punctuation from CSS:

$('ul p[class=big]').style.fontSize = "24pt";

Setting multiple properties is slightly less convenient:

$('ul p[class=big]').style.fontSize = "24pt";
$('ul p[class=big]').style.color = "red";

or perhaps more efficiently but requiring subtler programming concepts (iterators and anonymous functions):

$('ul p[class=big]').each(function(i, elem) { = "24pt"; = "red";

Many browsers now support XPath directly, so even without jQuery one can do the query part pretty easily, followed by a plain Javascript for loop to modify all the found elements:

var paras = evaluate("//ul//p[@class='big']");
for (var i = 0; i < paras.length; i++) {
    paras[i].style.fontSize = "24pt";

The last several examples all do the same thing, apart from some of them setting color:red. So it is not the task itself that makes the step from CSS to (plain) Javascript so big. The step is required even for some very simple things: As noted earlier, CSS makes it trivial to insert a particular attribute (using attr()), but not to insert the element name in the same way.

An old adage recommends making the easy jobs easy, without making the hard jobs impossible.[4] CSS seems in some respects to have stopped at the comma. So moving to Javascript (or another programming language) is hard to avoid even for some simple tasks, and that means ascending a slope which is anything but gentle. The change in work can be far out of proportion to the change in functionality that motivates it.

The integration issue

jQuery significantly eases use of Javascript, in large part because it leverages users’ likely prior knowledge of CSS selectors. CSS users can use jQuery to wrap up a selector as $('...') and then fiddle with the action in regular Javascript. That’s very good, and surely contributes to jQuery’s popularity even though one is likely to learn the harder path of plain Javascript before discovering the simpler one of jQuery.

jQuery can greatly ease the transition from using CSS to using Javascript. Eventually one is likely to run into the limits of CSS selectors, needing to choose a more complex or subtle set of elements. But separating (and delaying) the need to learn XPath, XQuery, or DOM allows the new Javascript programmer a lower barrier to entry.

AJICSS intends to provide a similar ease: Where jQuery lets one use CSS selectors within Javascript, AJICSS lets one use Javascript expressions within CSS. By doing so, the AJICSS user also leverages the CSS selector mechanism, avoiding the need to program functional equivalents of it in Javascript.

AJICSS may also help by keeping closely-related Javascript and CSS together. Many tasks requiring Javascript also require some CSS. Commonly, the same sets of elements the Javascript touches have CSS rules too, but they live in different places. Maintaining related things in multiple places is inconvenient and error-prone. There are actually more things to keep in sync than CSS and Javascript: both depend heavily on the HTML. Changing any of these may necessitate changing all the others in sync, which is difficult over time. With AJICSS one sees the Javascript right there in the CSS. Even if the Javascript there merely calls functions defined elsewhere, the fact that Javascript and CSS are operating on the same elements is clear, explicit, and hard to miss.

AJICSS embedding

AJICSS’[5] brings Javascript into CSS by using CSS’s notion of custom properties (whose names begin with --):

p:before {
    --dynamic:setCSS('content', startTag());

This is an AJICSS rule for the show tags example discussed above. It is a regular CSS rule, with a regular CSS selector, that applies to exactly the same elements (according to the CSS cascade, inheritance, importance rankings, @media constraints, etc.). It even includes two regular CSS properties, that CSS handles in precisely the normal manner.

Without the AJICSS library, the rule works except that the --dynamic... part is (essentially) ignored. But when loaded, the AJICSS library detects rules that set the --dynamic property, and treats them specially. When requested, AJICSS will visit each such element, extract the value of this property, and pass it to Javascript to evaluate, with the special variable dynTarget set to the specific current element.

By default, this all happens when the document has been loaded, but any other event can be used to trigger it if desired. In this case, the setCSS() Javascript function is called. That function is provided by AJICSS, and sets the CSS property named by its first argument (here content), to the value specified by its second argument. The second argument here is a call to the AJICSS startTag() function, which generates the start-tag for the current element.[6]

startTag() is not a general mechanism — it is about the same as if CSS were tweaked to add a startTag() function like its existing attr() function. The general mechanism is --dynamic, because it provides a way of inserting arbitrary Javascript. For example, a start-tag excluding attributes can be had without using startTag() and without writing a similar Javascript function:

p:before {
    --dynamic:setCSS('content', '<' + dynTarget.nodeName + '>');

Because AJICSS evaluates --dynamic separately for each element it applies to, the rules above need not change to apply to various element types, or even to *:before (unlike the example above which needed separate rules for every distinct element type in order to insert the correct type name for each).

If later changes should make the tag obsolete, a single call to AJICSS will update everything. Most AJICSS convenience functions are clever about resetting the document state on each update, so (for example) transclusions don’t transclude themselves if re-run.

Dynamic control of layout

Javascript can be coded directly within the --dynamic property, or call functions and libraries defined elsewhere. Even custom functions tend to be much simpler than they would be without AJICSS in play. For example, most of the setBigParas() example above was devoted to finding and traversing the right elements, but AJICSS makes that part free because it runs from within a CSS rule — all the selection semantics have already happened (in the familiar CSS fashion). AJICSS provides the correct current element via the dynTarget variable:

p:before { --dynamic:setCSS('content',
    dynTarget.parentNode.nodeName + "/" + dynTarget.nodeName);

Because this is Javascript, the whole DOM is available if needed (as are libraries like jQuery if requested). The code can refer to forms to let the user control layout or other features. The following gets the contents of a typing box with ID="sizeBox", doubles whatever number is in it, and sets the font-size of all p class="stretchy" elements to that:

p.stretchy {
    --dynamic:setCSS('font-size', ($('#sizeBox')[0].value * 2) + "pt");

To get the font size to change when the user changes the typing box, a call to updateDynamicElements() would be attached to an event such as onchange() in the usual Javascript or jQuery way (see above).


Transclusion, short for transparent inclusion, is a hypertext technique named by Ted Nelson [Nel81]. It involves copying some part of some source document(s) into a new context — much like quotation in general. Transclusion is generally taken to imply that the copy happens dynamically: if the source changes, the transcluded copy changes too; perhaps in real time like multi-user edits in Google docs, but at least when the destination is viewed again or refreshed.

The first hypertext systems to support transclusion were developed by Andy van Dam’s team at Brown University: HES, built in close collaboration with Ted Nelson, and HES’s successor FRESS [Cai99, DeR99]. FRESS authors could create not only jumps (on-request traversals much like HTML <a>), but also splices, which were taken with no user interaction required, functioning as a dynamic include. Like other FRESS constructs, both could be annotated with arbitrary lists of keywords that could be tested in order to reconfigure documents in small or large ways on the fly (more on this below).

CSS gives a useful taste of transclusion, but not a full meal: The content property usable on :before and :after pseudo-elements [Çel, Ete] inserts things at the beginning and/or end of an existing element, as discussed earlier for inserting quotation marks. But there are strong limitations of several kinds:

  • First, what data can be retrieved. Beyond constant strings, only a few other very specific items: properties of the current element such as attr() and counter(); depth-based open-quote, close-quote, no-open-quote, and no-close-quote; and url() (apparently only URLs for images). There is no way to extract data from elsewhere, whether an attribute of some other element, or a portion of some remote document.[7]

  • Second, what can be inserted. All of the retrievable things except URLs, result in an unstructured and unformatted text string (the :before or :after as a whole can then be formatted, but not any distinct portions of it). Testing indicates that url() can transclude an image, but not a text file, HTML document, or PDF (in Firefox, Chrome, and Opera; Safari embeds PDF but may have some problems afterward).[8] For images, CSS properties such as width height appear to be inoperative.

  • Third (and perhaps less important), where the transcluded data is positioned. :before and :after place it just inside the element they modify; there is no comparable feature for inserting something just outside, which can be important for layout.

AJICSS provides a transclude() function that addresses several of these limitations. It can pull in any HTML (or XML) fragment. The following AJICSS rule applies to the element with ID pullQuote, and places within it (like :before) a coy of the node with ID someID:

#pullQuote {
    --dynamic:transclude('before', getNode("#someID"));

The entire subtree of nodes is copied into the new context, so is rendered with all the usual capabilities, inheriting from the local context, not the origin context. If inheriting from the remote context proves important for some cases, it could be managed, but would require a considerably more complex version of transclude().

AJICSS also wraps the transcluded structure in a new div element (or more precisely Node), with a special class attribute value. Thus, CSS rules can be written that apply only to elements that have been transcluded (most obviously, one might wish to identify them as transclusions, perhaps by colorizing, adding border, etc.).

The nodes can also be processed with any Javascript code desired during the process, such as to combine, filter, or excerpt:

#pullQuote {
    --dynamic:transclude('before', myTransform(getNode("#someID")));

transclude() supports several targets: before and after place the nodes just inside the transcluding element (analogous to :before and :after in CSS); prepend and append place them just outside (the names match somewhat-related jQuery functions), and html and text replace the entire content of the transcluding element (in effect, its innerHTML or innerText DOM property).

The structure is actually copied, so will not change in real time if the source changes. However, additional Javascript can be used to have AJICSS update all dynamic properties when some event happens, such as editing the source.

It should be noted that transclusion necessarily raises some deeper semantic issues:

  • Generated content in general has debatable status: how should search engines treat a search for (say) a list item number and text? The number (or another marker) may be clearly visible to the user, yet it may not exist in the HTML. Should a search engine (or a browser’s Find command) see it?[9]

  • Generated, hidden, or moved data changes context relationships which a search may refer to. A classic example involves footnotes embedded in mid-sentence. The words preceding and following the footnote are logically (or at least grammatically) adjacent, and should be treated as such, despite however much text in the footnote physically intervenes. For example, quoting a series of words in some search interfaces requires them to occur together, and a footnote in between should probably not prevent such a match. HTML del and change-tracking in shared word processor documents pose similar problems.

  • Multiple copies of the same information may raise problems for search engines that use statistical methods based on word-frequency: the search may find a different number of instances of things.


CSS could add the equivalent of some AJICSS feature(s). For example, it would be trivial to add nodeName() and/or startTag() functions to use with content, along the lines of the current attr(). Some such additions may be useful, but they are not general. Some users may favor showing only certain selected attributes (such as ID and class), which requires something like adding a parameter to specify which ones, or concatenating together individual attributes. Suppressing empty or unset attributes (which are not the same thing) requires a way to test such conditions and respond accordingly. Snipping off a common prefix from all IDs for brevity requires some kind of substring operation. Showing just the font-family portion of HTML style attributes requires string searching or regex changes. None of these requirements is obscure or (in principle) complex; but the possibilities are unbounded.

General programming mechanisms such as functions, scoped variables, DOM access, and a full suite of operators are necessary for some needs, but CSS need not expand into its own full-fledged programming language. While some additions are no doubt justified, adding too many (particularly piecemeal) would risk creating a maze of twisty little features, all different. AJICSS or a similar mechanism can provide a fairly non-intrusive hook, through which CSS can leverage Javascript without users having to go all the way to a full Javascript solution.

General programming mechanisms such as function definition or generally-applicable arithmetic and logical operators may be more complex than individual tweaks.[10] Learning general mechanisms often pays off by making it almost trivial to learn later applications of the same (or symmetrical) mechanisms.

The analogy of spreadsheets

A similar example of easing the on-ramp to programming, is spreadsheets. Spreadsheet users can start out by learning a near-trivial conceptual model: There are documents, and each document consists of a grid of cells to type things in. Cell names are on-screen all the time, so users quickly add the idea of lettered columns and numbered rows, even before they need to use them in expressions. Later they learn to apply word-processor-like formatting to those cells (an addition that invalidates little if any of their prior model).

When users need more, it is a fairly small step to put expressions instead of constants in cells (this is analogous to what AJICSS enables in CSS). Users just learn to start the value with =, and this new notion of expressions need only include a few things (at least to begin):

  • constants like numbers and quoted strings (using notation learned in grade school)

  • functions (using notation learned in or shortly after grade school)

  • references to other cells or blocks of cells, like A1 or B1B10 (using notation learned playing Battleship®).

Spreadsheets provide very many functions, but all have much the same syntax and users only need learn particular ones as needed. Even learning only SUM() goes a long way, and is itself easy because it is pretty much the same concept as sum from grade school. Accountants (for example) need more, but already know many of the same financial functions (such as for compound interest), so learning those is not difficult. Occasionally an unfamiliar or perhaps outright odd function is needed, but by that time general notions such as nested function, argument, and cell reference have likely become familiar, so the increment remains small.

Learning all the functions available in a modern spreadsheet would be quite difficult and time-consuming, but almost no one needs to do that. Since almost everything one can do inside a cell is a function, and functions are named, one can easily look things up. Spreadsheets may be inelegant in various ways, but they do have the virtues of a gradual rather than abrupt learning curve and of minimal syntax.

Spreadsheets also divide the transition to programming in a useful way: Users learn to use functions, parameters, and datatypes without ever having to learn to create new functions. This minimizes the need for many of the subtler or more abstract concepts of real programming: dummy variables, recursion, iteration, many scope issues, mutability, calling by name vs. reference, etc.

AJICSS, similarly, aims to leverage what a CSS user already knows, to ease implementation of some Javascript tasks. A very simple application of this is emulating the spreadsheet feature of turning a cell red (or parenthesizing it, or whatever) when its numeric value is negative. In AJICSS this is fairly simple[11]:

.balanceCell {
    --dynamic::setCSS('color', (dynTarget.value < 0) ? 'red':'black');

AJICS provides a related library intended to make HTML tables almost as simple to access as spreadsheets. First, AJICSS provides Javascript functions to retrieve particular rows, columns, cells, or rectangular lists or blocks of cells from HTML tables, simply by their row and column numbers. It quietly handles special cases that come up: spans that throw off the numeric position of columns, table headers vs. bodies, whitespace or other things between cells, and so on.

AJICSS can also interpret cell references in the same form as spreadsheets: J10K20 can be used to get those cells from the current table (to address a different table an ID or other mechanism to specify the particular table is also needed). This table support can be used within --dynamic property values. Thus, AJICSS rules can look much like spreadsheet expressions, whose results are then transcluded into the cells. This is much easier than writing procedural Javascript to collect cells via IDs or counting, do a manual calculation, and save the result.

AJICSS also provides many of the same functions as popular spreadsheets, with the same names and arguments (some already exist in Javascript, particularly math functions such as sin()). Expressions can go in the stylesheet, or right on the cells they apply to, again avoiding the problem of keeping distant things in sync.

Content changes

AJICSS supplies an easy way to make changes to all the text content under a given element. The replaceText() function applies a regex change in all descendant text nodes. For example, to modernize the English medial s (U+017F or 'ſ') to modern s (the modern variable could in turn be set via a button to enable turning the change on and off at will):

#oldStyle p{
    --dynamic:if (modern) replaceText(/ſ/g, "s");

The similar tagText() function can tag all matches to a given regex, wrapping them in a specified tag, with a specified class. This can even be used to create links from all content matching something, putting the matched content into the URL as well as keeping it in the text.

Because such changes (as well as transclusions) can lead to odd punctuation changes at the boundaries, AJICSS provides punctuateText(), which can ensure that each affected element ends with a specified punctuation sequence, first removing any existing ending punctuation.

Display keywords

FRESS had a display keyword feature, permitting arbitrary keywords on any or all blocks. Users could set a display keyword request string to an arbitrary Boolean expression over the keywords, for example poetry and romantic and not iambic . Blocks were then visible only if their keywords made the expression true. There were also facilities for setting weights and values, for example importance=5 or author=milton.

The most obvious use of this may be stretchtext, where various text portions are annotated with numeric levels of importance or intended audience or difficulty, and the user can then choose how much information they want.

FRESS keywords could also be applied to jumps and to splices, making them available (or not) traverseal. Since splices were automatically followed (when active at all given the display keyword expression in effect), keyword filtering on them could completely reconfigure and rearrange the document.

Display keywords were also used to select parts of the document with certain characteristics. For example, a sacred text might have elements for the original language and a translation interleaved, along with non-canonical section headings, reference numbers, and so on. Each kind of thing could be marked with corresponding keywords, and the user could then choose which things to see. For example, a sacred text edition with original-language and English translation, commentary, and verse-numbering included and marked, could be filtered with an expression like english && canonical || verseNumber.

AJICSS provides the infrastructure for this, accessible via an skd() function (named after the FRESS set keyword display command), which evaluates a Boolean expression over each element’s class tokens and modifies its formatting accordingly (such as by hiding some elements).

CSS hiding

For display keywords, stretchtext, and other capabilities, hiding document portions is essential. CSS provides an astonishing variety of ways to do that, such as:

{ display:hide; }
    { visibility:hidden; }
    { font-size:0pt; }
    { position:absolute; top: -9999px; left: -9999px; }
    { max-width:1pt; overflow:hidden; }
    { cliprect(0, 0, 0, 0); }
    { clip-path: rect(10px, 20px, 30px, 40px); }

Each method has different effects and limitations, such as whether they can be overridden by descendant elements; whether the hidden nodes still take up space; how hidden content interacts with screen-readers, search, and other features; and so on. Setting font-size:0pt seems to be the best solution for a FRESS-like display keywords application.

How could CSS integrate similar functionality?

If CSS sought to add a hook for general programmability, it could do so while retaining its current overall model:

selector -> { property:value }*

value is already expression-like in that it can take a few functions, mainly calc() and var() (plus others for particular properties). The existing calc() feature already sounds general, but (at least at present) it cannot retrieve or extract much information from the document, or make changes to the DOM (for example, in response to HTML forms, Javascript code, etc.).

Changing calc() to support general Javascript, or adding a new function to do so, would not be technically difficult for standard-writers or implementors. By merely connecting up Javascript, CSS would entirely avoid having to invent or implement a new language and libraries.

With such a hook, users could accomplish a lot more right in their CSS, and with much less code than doing the same things directly in Javascript. This would move a bit closer to Wall’s adage, by making hard things possible (not to mention making many easy things easy rather than merely possible). It might also relieve pressure on CSS always to add the next little bit.

A concrete example: make the font size to rise with increasing line width, but less than linearly. If CSS permitted it, something like this would suffice[12]:

p { line-height: javascript(log( + "em"); }

Of course, for CSS dimensions there must be a way to specify units. This example uses the usual Javascript concatenation operator, +, to append a unit name (em) to the end of the calculated value. It could as easily use an additional parameter, or other methods.

With an approach generally like this, CSS need not invent or maintain a programming language. Javascript can already get and set CSS properties; CSS would merely need to set a variable (such as target) and then call the Javascript eval() function.

AJICSS implementation

AJICSS is written in Javascript and works across the major browsers, except IE which seems not to support custom CSS properties. It has not been tested with Oxygen. It typically has no noticable impact on performance, though it is certainly possible to use Javascript code or to have documents large enough to become slow.

AJICSS leverages CSS’s selection mechanisms without change.

On startup, AJICS scans the document and examines the computed style of each element (the final value after CSS has done inheritance, rule ranking, and its other operations). It keeps a list of all elements that have a --dynamic property.

Because CSS defines all custom properties to be inherited, AJICSS will see --dynamic on all descendants of any element that has it. This is usually not desirable for AJICSS. For example, if a paragraph transcludes something, its descendant elements should probably not do so as well. AJICSS therefore checks whether the (raw) --dynamic value on any element is the same as on its parent element, and if so ignores it. Particular --dynamic values can override that by prefixing * (which is removed before Javascript evaluates the value).

Whenever requested, AJICSS runs through all the elements with a value for --dynamic, and evaluates each value with dynTarget set to the current element. The actual returned result is discarded, so it is typical to call some function that effects the document, such as setCSS() or transclude().

Javascript tends to need a lot of error checking to cover things like selectors that resolve to nothing; missing or NULL function parameters; converting strings to numbers; and so on. AJICSS covers a lot of this, avoiding crashes or messages in favor of just doing nothing. That makes it much easier to write safe code in the little space one typically expects for a CSS property value. For example, showTag() is, if less flexible, much simpler than iterating over all the attributes, re-coding quotation marks inside values, and so on. Likewise, transclude() does not die if an IDREF, XPointer, XPath, selector, or other reference fails; it just does not transclude anything, and AJICSS goes on to the next element with --dynamic set.

AJICS also simplifies the coding one must do within CSS by providing convenience functions. For example, getInheritedAttribute() searches upward until it finds an specified attribute with the specified name. In cases such as hiding with skd() or modifying content with replaceText(), the function automatically stashes the original information rather than completely deleting it, so the original state can be restored.


CSS is very flexible within its chosen scope, but is not really meant to be a programming language. It has only very restricted conditional logic, and almost no access to document structure in general (selectors utilize document structure, but do not manipulate it). Some tasks of course require such features, and are therefore done in Javascript instead.

Users quite commonly want effects that require only a little beyond what CSS provides. For example, checking a fancier condition and then setting one or more CS properties. To do so they involves stepping up to a full programming language, mastering a very different way of selecting and modifying things, and taking on logistical tasks such as accessing and applying the code at the right times, keeping CSS, HTML, and Javascript changes in sync, and so on.

AJICSS instead provides a way to embed Javascript directly within a CSS custom property (conventionally named --dynamic. AJICSS finds such properties and evaluates them wherever applicable and whenever requested. It also provides several pre-packaged Javascript functions to use, such as for setting a CSS property in response to some test; hiding or showing elements based on their keywords; and accessing tables in spreadsheet-like fashion rather than by counting cells and spans manually. This approach greatly simplifies using Javascript, in part by leverages CSS selectors that are (typically) already in use.

AJICSS can make it easier to move from doing just CSS to doing programmatic tasks, and to deploy some useful and interesting hypertext capabilities that have long been neglected, such as more general transclusion, stretchtext, conditional display, and content-responsive formatting.


[Ber] Berners-Lee, Tim. April, 1997. URI References: Fragment Identifiers on URIs. Cambridge, MA: World Wide Web Consortium.

[Bos] Bos, Bert, et al. 07 June 2011. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1). W3C Recommendation (edited in place 12 April 2016 to point to new work).

[Boy] Boyer, John and Marcy, Glenn. 1989. Canonical XML Version 1.1 W3C Recommendation 2 May 2008. Cambridge, MA: World Wide Web Consortium.

[Cai99] Cailliau, Robert and Helen Ashman. 1999. Hypertext in the Web - a History. ACM Computing Surveys 31(4). doi:

[Çel] Çelik, Tantek, et al. 30 January 2018. Selectors Level 3 W3C Candidate Recommendation.

[DeR99] DeRose, Steven and Andries van Dam. 1999. Document structure and markup in the FRESS hypertext system. Markup Languages: Theory & Practice 1(1), January 1999: 7-32. doi:

[EC] ECMA International. June 2018. Standard ECMA-262: ECMAScript® 2018 Language Specification. 9th edition. See also ISO/IEC 16262 and Javascript.

[EDr] W3C Editor (unnamed in draft). 21 June 2018. CSS Generated Content Module Level 3: Editor’s Draft.

[Ete] Etemad, Elika J. and Dave Cramer. 2 June 2016. CSS Generated Content Module Level 3 W3C Working Draft.

[Gar] Garrett, Jesse James. February 18, 2005. Ajax: A New Approach to Web Applications.

[Get] Gettier, E. L. 1963. Is Justified True Belief Knowledge? Analysis 23: 121-3. doi:

[jQuery] jQuery. Home page:

[Nel81] Nelson, Ted. 1981. Literary machines. Sausalito, California: Mindful Press.

[Wall] Wall, Larry, Christiansen, Tom, and Schwartz, Randal. 1996 Programming Perl. 2nd Edition. Boston: O’Reilly. ISBN 13:9781565921498.

[1] An anonymous reviewer pointed out that this can also be done using content:open-quote, etc., which also keep track of nesting level and some internationalization issues.

[2] One might also pre-process CSS, for example expanding a smaller number of meta-rules into a larger number of plain CSS ones.

[3] Though there is no universally accepted definition of programming, CSS lacks many things like explicit flow of control, Turing completeness, general variables, functions, etc. that are nearly ubiquitous among clear cases of programming languages, and must be understood and managed almost constantly in real programs.

[4] Often attributed to Programming Perl [Wall], but it may well go back farther.

[5] To be precise, the last 3 letters are not Latin but Greek: a lunate sigma and two stigmas.

[6] One can pass any element to startTag() to get its start-tag; dynTarget is just the default.

[7] A server that supports AJAX [Gar] or a browser that’s clever about HTML fragment identifiers [Ber] could retrieve any of these, but those are not matters of CSS, much less of the content property. Even then, only an image or text has effect (not an HTML or DOM structure, etc. — see next item).

[8] The CSS 3 Generated Content Module (a Working Draft dated 2 June 2016) [Ete], adds features such as dot-leaders, and section 2.2 allows multiple comma-separated URLs as well as a contents keyword which seems intended to shift an element’s contents to one of its pseudo-elements. The Editor’s Draft dated 21 June 2018 [EDr] appears to delete most of 2.2, though it adds a / delimiter to separate alternative content for accessibility. The relationships of  , ,, and / delimiters are, at least to this author, unclear.

[9] Of course the marker may, alternatively, exist statically in the HTML, and just happen to look like an auto-generated list marker. Or it may exist in the HTML but be hidden, and what the user sees in the browser is not the very number from the source, but an auto-generated equivalent. This raises a Gettier-like problem [Get].

[10] Only may be, because tweaks are often limited to specific contexts or have other idiosyncratic restrictions (there are many examples in both CSS and other systems). Such non-symmetries add complexity and detract from learnability as compared to operators that are applicable generally and consistently. In effect, one general feature often provides the same functionality as a large number of specific tweaks, amortizing perhaps-greater initial complexity.

[11] The ?...: operator here produces red if the expression to the left of ? comes out true, and black otherwise, much like IF(expr, "red", "black") in some spreadsheets).

[12] Some might prefer that the entire argument to javascript() be quoted.


Berners-Lee, Tim. April, 1997. URI References: Fragment Identifiers on URIs. Cambridge, MA: World Wide Web Consortium.


Bos, Bert, et al. 07 June 2011. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1). W3C Recommendation (edited in place 12 April 2016 to point to new work).


Boyer, John and Marcy, Glenn. 1989. Canonical XML Version 1.1 W3C Recommendation 2 May 2008. Cambridge, MA: World Wide Web Consortium.


Cailliau, Robert and Helen Ashman. 1999. Hypertext in the Web - a History. ACM Computing Surveys 31(4). doi:


Çelik, Tantek, et al. 30 January 2018. Selectors Level 3 W3C Candidate Recommendation.


DeRose, Steven and Andries van Dam. 1999. Document structure and markup in the FRESS hypertext system. Markup Languages: Theory & Practice 1(1), January 1999: 7-32. doi:


ECMA International. June 2018. Standard ECMA-262: ECMAScript® 2018 Language Specification. 9th edition. See also ISO/IEC 16262 and Javascript.


W3C Editor (unnamed in draft). 21 June 2018. CSS Generated Content Module Level 3: Editor’s Draft.


Etemad, Elika J. and Dave Cramer. 2 June 2016. CSS Generated Content Module Level 3 W3C Working Draft.


Garrett, Jesse James. February 18, 2005. Ajax: A New Approach to Web Applications.


Gettier, E. L. 1963. Is Justified True Belief Knowledge? Analysis 23: 121-3. doi:


jQuery. Home page:


Nelson, Ted. 1981. Literary machines. Sausalito, California: Mindful Press.


Wall, Larry, Christiansen, Tom, and Schwartz, Randal. 1996 Programming Perl. 2nd Edition. Boston: O’Reilly. ISBN 13:9781565921498.

Author's keywords for this paper:
Hypertext; Hypermedia; FRESS; CSS; Javascript