The Art of CSS

# Simple, not easy

  • CSS syntax is relatively simple to learn, or at least lookup
  • but its semantics is not easy
  • let's review the basics: cascade, inheritance, and custom properties

# Glossary Reminder

selector { /* declarations block */
property : value; /* declaration */
another : value; /* declaration */
}

# The cascade

  • property values of an element can be defined by multiple declaration blocks
  • declaration blocks can have different specificity
  • more specific declarations have precedence
  • if same specificity, use source order: last one have precedence

# CSS Cascade Level 0001

  • HTML is annotated with elements and pseudo elements
  • carry semantic value
  • style on these
  • gives a low fidelity look-and-feel

# CSS Cascade Level 0010

  • annotate further with classes, pseudo-classes, and attributes
  • ARIA attributes especially, increases semantic richness
  • style on these
  • gives a medium fidelity look-and-feel

# CSS Cascade Level 0100

  • annotate further with IDs
  • no semantic value outside of application
  • style on these
  • gives a high fidelity look-and-feel

# CSS Cascade Level 1000

  • annotate with inline styles
  • no semantic value
  • completes the look-and-feel

# Example: constructive interference

  • properties cascade constructively from multiple selector blocks, to build the final style
p    { font-style  : italic; }
.foo { font-family : serif; }
#bar { background : blue; }
<p class="foo" id="bar" style="color:tomato;">
italic, serif, blue background, tomato text
</p>

# Example: destructive interference

  • or destructively, cancelling each other out
p    { color : red;   }
.foo { color : green; }
#bar { color : blue; }
<p class="foo" id="bar" style="color:tomato;">
tomato text
</p>

# Example: finding a balance

  • or even both constructive and destructive
/* level 0001 */
p { color: red; }

/* level 0002 */
p + p { background: green; color: blue; }
<p>red text</p>

<p>
green background (constructive),
blue text (destructive)
</p>

# Stylesheet Origin

  • origins of stylesheets, with increasing precedence
    • User Agent: reasonable default look and feel
    • User: custom settings (eg. stylish)
    • Author: from website designers
    • Author !important
    • User !important
    • User Agent !important (rare)
  • users have ultimate control over how they want to view sites
    • eg: adblocking, or removing annoying headers/footers

# Inheritance

  • DOM has hierarchical structure (thus inheritance)
  • elements can inherit undefined styles from ancestors
  • some like color inherit by default, others like display do not
  • force/change inheritance with:
    • a new value (what we normally do)
    • inherit: to use DOM parent's value
    • initial: according to CSS specifications
    • unset: equivalent to inherit if normally inherited, initial if normally not

# Example: Single Responsibility

  • single responsibility principle applied to selectors
    • declaration blocks should be readable on its own
    • easy to reason without looking at output
  • use composition to build an element's style
  • responsibility can be based on hierarchical roles
.as-child  { /* eg: position: absolute; */ }
.as-self { /* eg: color: tomato; */ }
.as-parent { /* eg: display: flex; */ }
.as-peer { /* eg: margin: 1em; */ }
<div class="as-child as-self as-parent as-peer">
content
</div>

# Example: CSS Hierarchy

  • describe hierarchy in CSS to reduce HTML verbiage
  • sibling relationships too
.foo     { /* eg: display: flex; */ }
.foo > * { /* eg: flex: 1 0 50%; */ }
<div class="foo">
<div>content</div>
<div>content</div>
<div>content</div>
</div>

# CSS and HTML are co-dependent

  • interchangeably shift responsibility between CSS and HTML
  • cannot write one without thinking about other

# Example: complexity in CSS

  • CSS selectors can be complex and contain many declarations per block
  • while HTML has barely any annotations
div > p {
font-style : italic;
font-family : serif;
background : blue;
color : tomato
}
<div>
<p>
italic, serif, blue background, tomato text
</p>
</div>

# Example: complexity in HTML

  • CSS can be really simple (from Atomic CSS, or see also Tailwind CSS)
  • while HTML is heavily annotated with CSS classes
.Fs(i)     { font-style  : italic; }
.Ff(serif) { font-family : serif; }
.Bg(blue) { background : blue; }
.C(tomato) { color : tomato }
<div>
<p class="Fs(i) Ff(serif) Bg(blue) C(tomato)">
italic, serif, blue background, tomato text
</p>
</div>
  • find balance such that both documents are semantic and DRY

# Custom properties

  • defined just like any other property
  • obeys same cascade rules
  • inherited by default
  • can provide an optional fallback

# Example: dynamic styling

  • data-driven styling from HTML
  • last example looks like inline-styles, but …
.foo {
color: var(--foo-color, tomato);
}
.foo--modified {
--foo-color: firebrick;
}
#foo-target {
--foo-color: orchid;
}
<p class="foo">
tomato text (fallback to default)
</p>
<p class="foo foo--modified">
firebrick text, specificity 10
</p>
<p class="foo" id="foo-target">
orchid text, specificity 100
</p>
<p class="foo" style="--foo-color: seagreen;">
seagreen text, specificity 1000
</p>

# Example: Property dependencies

  • when a property depends on another: the value is computed once per element, and the computed value inherited
  • much more powerful than inline-styles; becomes function-like
  • graceful fallback to unset in unsupported browsers
.foo {
--foo-background: linear-gradient(
var(--foo-angle),
seagreen,
tomato
);
background: var(--foo-background);
}
<p class="foo">no background (no default, "unset")</p>
<p class="foo" style="--foo-angle: 10deg">dynamic</p>
<p class="foo" style="--foo-angle: 20deg">dynamic</p>
<p class="foo" style="--foo-angle: 30deg">dynamic</p>

# The Art of CSS

  • CSS is meant for designing consistent design systems
    • global first, decrease scope (increase specificity) as required
  • developers tend to focus on components, result in per-component styles
    • local first, increase scope as required
  • but CSS + HTML are co-dependent documents
    • should be co-authored, shift responsibility accordingly to increase semantic-ness

# Summary

  • embrace the cascade; apply common case, then exceptions
  • embrace the DOM inheritance hierarchy and sibling relationships; describe it in the CSS to reduce HTML verbiage
  • embrace composition of (usually hierarchical) roles to build styles
  • embrace the dynamic nature of custom properties; use it for data-driven documents