Slide 1

Slide 1 text

The Edge of CSS by Tommy Hodgins

Slide 2

Slide 2 text

Have you ever found yourself at the edge of what CSS can do?

Slide 3

Slide 3 text

Signs you’re on the edge • using polyfills for unsupported features

Slide 4

Slide 4 text

Signs you’re on the edge • using polyfills for unsupported features • generating unique IDs or classes for elements

Slide 5

Slide 5 text

Signs you’re on the edge • using polyfills for unsupported features • generating unique IDs or classes for elements • duplicating code only to change breakpoints

Slide 6

Slide 6 text

Signs you’re on the edge • using polyfills for unsupported features • generating unique IDs or classes for elements • duplicating code only to change breakpoints • so many more (see Sisyphus)

Slide 7

Slide 7 text

In situations where your only option is writing custom JavaScript to apply a style to an element…

Slide 8

Slide 8 text

Think how CSS could be extended to reach that situation instead

Slide 9

Slide 9 text

What if you could extend CSS in the browser to add new features and functionality?

Slide 10

Slide 10 text

What if instead of a CSS preprocessor you had a CSS reprocessor?

Slide 11

Slide 11 text

What is a CSS reprocessor?

Slide 12

Slide 12 text

A CSS reprocessor is a tool to recalculate your styles as often as needed after the page has loaded, and after user interaction

Slide 13

Slide 13 text

Thinking beyond the edge of CSS

Slide 14

Slide 14 text

My journey beyond the edge began in 2014 with the EQCSS project

Slide 15

Slide 15 text

The goal of EQCSS was to add the features I wanted to use to CSS so I could use them

Slide 16

Slide 16 text

The hard question:

Slide 17

Slide 17 text

What’s the smallest set of ideas that could be added to CSS to lead to the functionality I wanted?

Slide 18

Slide 18 text

Our 4 What-ifs

Slide 19

Slide 19 text

What if CSS Could • Set a scope for a selector like a @media query but for individual elements

Slide 20

Slide 20 text

What if CSS Could • Set a scope for a selector like a @media query but for individual elements • Add a block of CSS styles to the page when that selector is true

Slide 21

Slide 21 text

What if CSS Could • Set a scope for a selector like a @media query but for individual elements • Add a block of CSS styles to the page when that selector is true • Add responsive conditions to that selector

Slide 22

Slide 22 text

What if CSS Could • Set a scope for a selector like a @media query but for individual elements • Add a block of CSS styles to the page when that selector is true • Add responsive conditions to that selector • Evaluate JavaScript from the context of the element you are styling and use those values in the CSS you're applying to the page

Slide 23

Slide 23 text

With those four ideas added on top of what CSS already does many new responsive techniques become very simple to express

Slide 24

Slide 24 text

The amount of JavaScript required to add support for these four ideas is about 3000 lines of code, or about 3kb when minified and gzipped

Slide 25

Slide 25 text

But there’s no end to the amount of new responsive techniques possible…

Slide 26

Slide 26 text

New techniques possible • scoped styles • element queries & container queries • breakpoints based on element properties • variables scoped to individual elements • self-responsive HTML components • parent selector, previous element selector, etc

Slide 27

Slide 27 text

• element-based units • scroll-based responsive styles • smarter attribute selectors (number comparison) • make any element scalable • work easily with aspect ratios • polyfill and emulate unsupported CSS features • many more… (search ‘EQCSS’ on CodePen for 100+)

Slide 28

Slide 28 text

EQCSS has been used in production for 2 years

Slide 29

Slide 29 text

EQCSS allows us to • Build layout-independent design components

Slide 30

Slide 30 text

EQCSS allows us to • Build layout-independent design components • Scope, isolate, and transplant existing layout parts from one project to another

Slide 31

Slide 31 text

EQCSS allows us to • Build layout-independent design components • Scope, isolate, and transplant existing layout parts from one project to another • Support modern responsive layouts in older browsers

Slide 32

Slide 32 text

EQCSS allows us to • Build layout-independent design components • Scope, isolate, and transplant existing layout parts from one project to another • Support modern responsive layouts in older browsers • Keep our CSS short and our workflow simple

Slide 33

Slide 33 text

Moving beyond The edge of CSS

Slide 34

Slide 34 text

How EQCSS works • If any @element queries are found, the selector(s), condition(s), and styles are extracted

Slide 35

Slide 35 text

How EQCSS works • EQCSS recalculates on: DOM ready, scroll, resize, input, click, mousedown, mousemove, mouseup

Slide 36

Slide 36 text

How EQCSS works • Any time there is at least 1 element in the DOM matching a selector in the selector list of an @element query • And all conditions are true (if any included) • Add the block of styles to the page

Slide 37

Slide 37 text

How EQCSS works • And while we’re applying CSS to the page: provide a way to evaluate JavaScript from the context of each element we’re styling

Slide 38

Slide 38 text

With @media queries the most common responsive features are min/max width and height, but with @element queries, many new responsive features make sense

Slide 39

Slide 39 text

Responsive conditions • width • height • characters (number of characters of text content) • lines (number of lines of text) • children (number child elements) • scroll-x (horizontal scroll position) • scroll-y (vertical scroll position) • aspect-ratio • orientation

Slide 40

Slide 40 text

Inside the scope of the @element query, new selectors are possible

Slide 41

Slide 41 text

Meta-Selectors • $this is the scoped element • $parent is the parent of the scoped element • $prev is the element before the scoped element • $next is the element after the scoped element

Slide 42

Slide 42 text

Evaluating the properties of the elements as we style them adds the possibility for new units

Slide 43

Slide 43 text

Similar to how viewport units (VW, VH, VMIN and VMAX) refer to viewport-based percentages, what if element-based units referred to dimensions of individual elements

Slide 44

Slide 44 text

Element-based units • EW is 1% element width • EH is 1% element height • EMIN is 1% element minimum length • EMAX is 1% element maximum length

Slide 45

Slide 45 text

Eval • eval('') uses the output of JavaScript evaluated from the context of each element the query applies to anywhere inside the styles being added to the page

Slide 46

Slide 46 text

Some CSS+ Techniques

Slide 47

Slide 47 text

Imagine if CSS had more dynamic values like currentColor

Slide 48

Slide 48 text

What if you could use values like currentWidth or parentChildren in your CSS?

Slide 49

Slide 49 text

Many values like this, while out of reach of CSS, are simple to measure with JavaScript if you could evaluate from the context of each element you are styling

Slide 50

Slide 50 text

Imagine if an iframe had a way to scale height responsively based on its own aspect ratio

Slide 51

Slide 51 text

iframe { width: 100%; height: calc(currentWidth / (16/9)); }

Slide 52

Slide 52 text

currentWidth is kind of like 100ew

Slide 53

Slide 53 text

@element iframe { $this { width: 100%; height: calc(100ew / (16/9)); } }

Slide 54

Slide 54 text

What if CSS could access each iframe’s own width and height attributes, something kind of like attr(width) or attr(height)

Slide 55

Slide 55 text

@element iframe { $this { width: 100%; height: calc(100ew / (attr(width)/attr(height))); } }

Slide 56

Slide 56 text

We can access these values for $this element by using eval('width') and eval('height')

Slide 57

Slide 57 text

@element iframe { $this { width: 100%; height: calc(100ew / (eval('width')/eval('height'))); } }

Slide 58

Slide 58 text

Here eval('width') is giving us the same result in our CSS as el.width or el.getAttribute('width') would return in JavaScript

Slide 59

Slide 59 text

No matter what the current width, we can always scale the height of each iframe based on its own aspect ratio, determined by its own width and height attributes

Slide 60

Slide 60 text

What about a parent selector, or :has()?

Slide 61

Slide 61 text

Have you ever wanted to apply a style to an element that contains another element?

Slide 62

Slide 62 text

Suppose we want to make an h2 red, but only if it contains a strong element inside

Slide 63

Slide 63 text

This is possible with CSS using h2:has(strong), but unfortunately no web browsers support :has()

Slide 64

Slide 64 text

h2:has(strong) { color: red; }

Slide 65

Slide 65 text

This is equivalent to styling $this inside an element query for h2:has(strong) if it had support…

Slide 66

Slide 66 text

@element h2:has(strong) { $this { color: red; } }

Slide 67

Slide 67 text

Since we can’t use :has(), could we use querySelector() inside eval('') to check for strong tags?

Slide 68

Slide 68 text

@element h2 { eval('querySelector("strong") && "$this"') { color: red; } }

Slide 69

Slide 69 text

Inside our element query for h2 tags, if querySelector('strong') returns true, the rule applies to $this

Slide 70

Slide 70 text

…Or maybe you could apply a style to the parent of every strong tag if the $parent is also an h2

Slide 71

Slide 71 text

@element strong { h2$parent { color: red; } }

Slide 72

Slide 72 text

Or another way to do the same thing would be to apply a style to the $parent of any h2 strong

Slide 73

Slide 73 text

@element h2 strong { $parent { color: red; } }

Slide 74

Slide 74 text

Comparing attribute values as numbers

Slide 75

Slide 75 text

CSS has an attribute selector that can select elements based on comparing values as strings

Slide 76

Slide 76 text

But what if you could compare attributes as numbers, like [value<0] or [value >=50]

Slide 77

Slide 77 text

What if you could use a selector like [type=number][value<0] to select inputs with values less than 0

Slide 78

Slide 78 text

[type=number][value < 0] { border: 1px solid red; }

Slide 79

Slide 79 text

We can use [value=0] and match the number 0 as a string, but not compare as a number with >, <, >=, or <=

Slide 80

Slide 80 text

@element [type=number] { eval('(value < 0) && "$this"') { border: 1px solid red; } }

Slide 81

Slide 81 text

Here we add a red border to every [type=number] element with a value < 0

Slide 82

Slide 82 text

Can we create automatic hyphenation based on an element's own width?

Slide 83

Slide 83 text

Imagine you want to set hyphens for heading elements when they are less than 35em wide, you may use a @media query like this

Slide 84

Slide 84 text

@media (max-width: 35em) { h1, h2, h3, h4, h5, h6 { hyphens: auto; } }

Slide 85

Slide 85 text

We can use an @element query to make the breakpoint relative to the width of each heading tag rather than the width of the browser’s viewport

Slide 86

Slide 86 text

@element h1, h2, h3, h4, h5, h6 and (max-width: 35em) { $this { hyphens: auto; } }

Slide 87

Slide 87 text

Now our heading tags will have hyphenation when any of them are less than 35em wide, regardless of the width of the browser

Slide 88

Slide 88 text

Think of how you might accomplish some of the the following ideas…

Slide 89

Slide 89 text

• change the background-position of a background image based on an element’s aspect-ratio

Slide 90

Slide 90 text

• change the background-position of a background image based on an element’s aspect-ratio • style an input that's empty (input:empty doesn't work in CSS)

Slide 91

Slide 91 text

• change the background-position of a background image based on an element’s aspect-ratio • style an input that's empty (input:empty doesn't work in CSS) • create scalable/responsive CSS border art

Slide 92

Slide 92 text

• change the background-position of a background image based on an element’s aspect-ratio • style an input that's empty
 (input:empty doesn't work in CSS) • create scalable/responsive CSS border art • style a textarea or input so it expands to fit as many characters or lines of text as are inside

Slide 93

Slide 93 text

To get started, add the following line of code to your project:

Slide 94

Slide 94 text

Slide 95

Slide 95 text

Reprocessing CSS

Slide 96

Slide 96 text

If you are going to try a CSS reprocessor, here are things to keep in mind

Slide 97

Slide 97 text

Which events should trigger recalculations? • things like: load, resize, input and keyboard, clicks and touch events, scroll on elements, etc. • can you only listen to events that make sense to the styles you are recalculating? • can you use resize observers for width/height? • do mutation observers help?

Slide 98

Slide 98 text

Is your reprocessor integrated inside a specific tech stack or tool, or does it exist as a plugin that runs in the browser like a shim or polyfill?

Slide 99

Slide 99 text

What’s your exit strategy or migration path for porting code away from the reprocessor if you need to at some point in the future?

Slide 100

Slide 100 text

THANKS! Visit elementqueries.com for demos, docs, & links