<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: css-custom-properties</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/css-custom-properties.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-04-03T15:53:52+00:00</updated><author><name>Simon Willison</name></author><entry><title>First look at the modern attr()</title><link href="https://simonwillison.net/2025/Apr/3/first-look-at-the-modern-attr/#atom-tag" rel="alternate"/><published>2025-04-03T15:53:52+00:00</published><updated>2025-04-03T15:53:52+00:00</updated><id>https://simonwillison.net/2025/Apr/3/first-look-at-the-modern-attr/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://ishadeed.com/article/modern-attr/"&gt;First look at the modern attr()&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Chrome 133 (released February 25th 2025) was the first browser to &lt;a href="https://developer.chrome.com/release-notes/133?hl=en#css_advanced_attr_function"&gt;ship support&lt;/a&gt; for the advanced CSS &lt;code&gt;attr()&lt;/code&gt; function (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/attr"&gt;MDN&lt;/a&gt;), which lets &lt;code&gt;attr()&lt;/code&gt; be used to compose values using types other than strings.&lt;/p&gt;
&lt;p&gt;Ahmad Shadeed explores potential applications of this in detail, trying it out for CSS grid columns, progress bars, background images, animation delays and more.&lt;/p&gt;
&lt;p&gt;I like this example that uses the &lt;code&gt;rows="5"&lt;/code&gt; attribute on a &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; to calculate its &lt;code&gt;max-height&lt;/code&gt; - here wrapped in a feature detection block:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;@supports&lt;/span&gt; (&lt;span class="pl-c1"&gt;x&lt;/span&gt;&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-en"&gt;attr&lt;/span&gt;(x &lt;span class="pl-en"&gt;type&lt;/span&gt;(&lt;span class="pl-c1"&gt;*&lt;/span&gt;))) {
  &lt;span class="pl-ent"&gt;textarea&lt;/span&gt; {
    &lt;span class="pl-c1"&gt;min-height&lt;/span&gt;&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-en"&gt;calc&lt;/span&gt;(
      &lt;span class="pl-en"&gt;attr&lt;/span&gt;(rows &lt;span class="pl-en"&gt;type&lt;/span&gt;(&amp;lt;number&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;)) &lt;span class="pl-c1"&gt;*&lt;/span&gt; &lt;span class="pl-c1"&gt;50&lt;span class="pl-smi"&gt;px&lt;/span&gt;&lt;/span&gt;
    );
  }
}&lt;/pre&gt;

&lt;p&gt;That &lt;code&gt;type(&amp;lt;number&amp;gt;)&lt;/code&gt; is the new syntax.&lt;/p&gt;
&lt;p&gt;Many of Ahmad's examples can be achieved today across all browsers using a slightly more verbose CSS custom property syntax.&lt;/p&gt;
&lt;p&gt;Here are the tracking issues for CSS values support in &lt;code&gt;attr()&lt;/code&gt; for &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=435426"&gt;Firefox&lt;/a&gt; (opened 17 years ago) and &lt;a href="https://bugs.webkit.org/show_bug.cgi?id=26609"&gt;WebKit&lt;/a&gt; (16 years ago).


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/chrome"&gt;chrome&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css"&gt;css&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/web-standards"&gt;web-standards&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css-custom-properties"&gt;css-custom-properties&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ahmad-shadeed"&gt;ahmad-shadeed&lt;/a&gt;&lt;/p&gt;



</summary><category term="chrome"/><category term="css"/><category term="web-standards"/><category term="css-custom-properties"/><category term="ahmad-shadeed"/></entry><entry><title>Minimal CSS-only blurry image placeholders</title><link href="https://simonwillison.net/2025/Apr/3/minimal-css-only-blurry-image-placeholders/#atom-tag" rel="alternate"/><published>2025-04-03T02:44:18+00:00</published><updated>2025-04-03T02:44:18+00:00</updated><id>https://simonwillison.net/2025/Apr/3/minimal-css-only-blurry-image-placeholders/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://leanrada.com/notes/css-only-lqip/"&gt;Minimal CSS-only blurry image placeholders&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Absolutely brilliant piece of CSS ingenuity by Lean Rada, who describes a way to implement blurry placeholder images using just CSS, with syntax like this:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;img&lt;/span&gt; &lt;span class="pl-c1"&gt;src&lt;/span&gt;="&lt;span class="pl-s"&gt;…&lt;/span&gt;" &lt;span class="pl-c1"&gt;style&lt;/span&gt;="&lt;span class="pl-s"&gt;--lqip:192900&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;That 192900 number encodes everything needed to construct the placeholder - it manages to embed a single base color and six brightness components (in a 3x2 grid) in 20 bits, then encodes those as an integer in the roughly 2 million available values between -999,999 and 999,999 - beyond which range Lean found some browsers would start to lose precision.&lt;/p&gt;
&lt;p&gt;The implementation for decoding that value becomes a bunch of clever bit-fiddling CSS expressions to expand it into further CSS variables:&lt;/p&gt;
&lt;pre&gt;[&lt;span class="pl-c1"&gt;style&lt;/span&gt;&lt;span class="pl-c1"&gt;*=&lt;/span&gt;&lt;span class="pl-s"&gt;"--lqip:"&lt;/span&gt;] {
  &lt;span class="pl-s1"&gt;--lqip-ca&lt;/span&gt;&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-en"&gt;mod&lt;/span&gt;(&lt;span class="pl-en"&gt;round&lt;/span&gt;(down&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-en"&gt;calc&lt;/span&gt;((&lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--lqip&lt;/span&gt;) &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-en"&gt;pow&lt;/span&gt;(&lt;span class="pl-c1"&gt;2&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;19&lt;/span&gt;)) &lt;span class="pl-c1"&gt;/&lt;/span&gt; &lt;span class="pl-en"&gt;pow&lt;/span&gt;(&lt;span class="pl-c1"&gt;2&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;18&lt;/span&gt;)))&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;4&lt;/span&gt;);
  &lt;span class="pl-s1"&gt;--lqip-cb&lt;/span&gt;&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-en"&gt;mod&lt;/span&gt;(&lt;span class="pl-en"&gt;round&lt;/span&gt;(down&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-en"&gt;calc&lt;/span&gt;((&lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--lqip&lt;/span&gt;) &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-en"&gt;pow&lt;/span&gt;(&lt;span class="pl-c1"&gt;2&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;19&lt;/span&gt;)) &lt;span class="pl-c1"&gt;/&lt;/span&gt; &lt;span class="pl-en"&gt;pow&lt;/span&gt;(&lt;span class="pl-c1"&gt;2&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;16&lt;/span&gt;)))&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;4&lt;/span&gt;);
  &lt;span class="pl-c"&gt;/* more like that */&lt;/span&gt;
}&lt;/pre&gt;

&lt;p&gt;Which are expanded to even more variables with code like this:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-s1"&gt;--lqip-ca-clr&lt;/span&gt;&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-en"&gt;hsl&lt;/span&gt;(&lt;span class="pl-c1"&gt;0&lt;/span&gt; &lt;span class="pl-c1"&gt;0&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-en"&gt;calc&lt;/span&gt;(&lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--lqip-ca&lt;/span&gt;) &lt;span class="pl-c1"&gt;/&lt;/span&gt; &lt;span class="pl-c1"&gt;3&lt;/span&gt; &lt;span class="pl-c1"&gt;*&lt;/span&gt; &lt;span class="pl-c1"&gt;100&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt;));
&lt;span class="pl-s1"&gt;--lqip-cb-clr&lt;/span&gt;&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-en"&gt;hsl&lt;/span&gt;(&lt;span class="pl-c1"&gt;0&lt;/span&gt; &lt;span class="pl-c1"&gt;0&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-en"&gt;calc&lt;/span&gt;(&lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--lqip-cb&lt;/span&gt;) &lt;span class="pl-c1"&gt;/&lt;/span&gt; &lt;span class="pl-c1"&gt;3&lt;/span&gt; &lt;span class="pl-c1"&gt;*&lt;/span&gt; &lt;span class="pl-c1"&gt;100&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt;));&lt;/pre&gt;

&lt;p&gt;And finally rendered using a CSS gradient definition that starts like this:&lt;/p&gt;
&lt;pre&gt;[&lt;span class="pl-c1"&gt;style&lt;/span&gt;&lt;span class="pl-c1"&gt;*=&lt;/span&gt;&lt;span class="pl-s"&gt;"--lqip:"&lt;/span&gt;] {
  &lt;span class="pl-c1"&gt;background-image&lt;/span&gt;&lt;span class="pl-kos"&gt;:&lt;/span&gt;
    &lt;span class="pl-en"&gt;radial-gradient&lt;/span&gt;(&lt;span class="pl-c1"&gt;50&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-c1"&gt;75&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt; at &lt;span class="pl-c1"&gt;16.67&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-c1"&gt;25&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--lqip-ca-clr&lt;/span&gt;)&lt;span class="pl-kos"&gt;,&lt;/span&gt; transparent)&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-en"&gt;radial-gradient&lt;/span&gt;(&lt;span class="pl-c1"&gt;50&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-c1"&gt;75&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt; at &lt;span class="pl-c1"&gt;50&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-c1"&gt;25&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--lqip-cb-clr&lt;/span&gt;)&lt;span class="pl-kos"&gt;,&lt;/span&gt; transparent)&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c"&gt;/* ... */&lt;/span&gt;
    &lt;span class="pl-en"&gt;linear-gradient&lt;/span&gt;(&lt;span class="pl-c1"&gt;0&lt;span class="pl-smi"&gt;deg&lt;/span&gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--lqip-base-clr&lt;/span&gt;)&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--lqip-base-clr&lt;/span&gt;));
}&lt;/pre&gt;

&lt;p&gt;The article includes several interactive explainers (most of which are also powered by pure CSS) illustrating how it all works.&lt;/p&gt;
&lt;p&gt;Their &lt;a href="https://github.com/Kalabasa/leanrada.com/blob/7b6739c7c30c66c771fcbc9e1dc8942e628c5024/main/scripts/update/lqip.mjs#L118-L159"&gt;Node.js script&lt;/a&gt; for converting images to these magic integers uses &lt;a href="https://www.npmjs.com/package/sharp"&gt;Sharp&lt;/a&gt; to resize the image to 3x2 and then use the &lt;a href="https://en.m.wikipedia.org/wiki/Oklab_color_space"&gt;Oklab perceptually uniform color space&lt;/a&gt; (new to me, that was created by Björn Ottosson in 2020) to derive the six resulting values.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=43523220"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/css"&gt;css&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css-custom-properties"&gt;css-custom-properties&lt;/a&gt;&lt;/p&gt;



</summary><category term="css"/><category term="css-custom-properties"/></entry><entry><title>APIs from CSS without JavaScript: the datasette-css-properties plugin</title><link href="https://simonwillison.net/2021/Jan/7/css-apis-no-javascript/#atom-tag" rel="alternate"/><published>2021-01-07T20:50:44+00:00</published><updated>2021-01-07T20:50:44+00:00</updated><id>https://simonwillison.net/2021/Jan/7/css-apis-no-javascript/#atom-tag</id><summary type="html">
    &lt;p&gt;I built a new Datasette plugin called &lt;a href="https://datasette.io/plugins/datasette-css-properties"&gt;datasette-css-properties&lt;/a&gt;. It's very, very weird - it adds a &lt;code&gt;.css&lt;/code&gt; output extension to Datasette which outputs the result of a SQL query using CSS custom property format. This means you can display the results of database queries using pure CSS and HTML, no JavaScript required!&lt;/p&gt;
&lt;p&gt;I was inspired by &lt;a href="https://css-tricks.com/custom-properties-as-state/"&gt;Custom Properties as State&lt;/a&gt;, published by by Chris Coyier earlier this week. Chris points out that since CSS custom properties can be defined by an external stylesheet, a crafty API could generate a stylesheet with dynamic properties that could then be displayed on an otherwise static page.&lt;/p&gt;
&lt;p&gt;This is a weird idea. Datasette's plugins system is pretty much designed for weird ideas - my favourite thing about having plugins is that I can try out things like this without any risk of damaging the integrity of the core project.&lt;/p&gt;
&lt;p&gt;So I built it! Here are some examples:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://latest-with-plugins.datasette.io/fixtures/roadside_attractions"&gt;roadside_attractions&lt;/a&gt; is a table that ships as part of Datasette's "fixtures" test database, which I write unit tests against and use for quick demos.&lt;/p&gt;
&lt;p&gt;The URL of that table within Datasette is &lt;code&gt;/fixtures/roadside_attractions&lt;/code&gt;. To get the first row in the table back as CSS properties, simply add a &lt;code&gt;.css&lt;/code&gt; extension:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://latest-with-plugins.datasette.io/fixtures/roadside_attractions.css"&gt;/fixtures/roadside_attractions.css&lt;/a&gt; returns this:&lt;/p&gt;
&lt;div class="highlight highlight-source-css"&gt;&lt;pre&gt;:&lt;span class="pl-c1"&gt;root&lt;/span&gt; {
  &lt;span class="pl-c1"&gt;--pk&lt;/span&gt;: &lt;span class="pl-s"&gt;'1'&lt;/span&gt;;
  &lt;span class="pl-c1"&gt;--name&lt;/span&gt;: &lt;span class="pl-s"&gt;'The Mystery Spot'&lt;/span&gt;;
  &lt;span class="pl-c1"&gt;--address&lt;/span&gt;: &lt;span class="pl-s"&gt;'465 Mystery Spot Road, Santa Cruz, CA 95065'&lt;/span&gt;;
  &lt;span class="pl-c1"&gt;--latitude&lt;/span&gt;: &lt;span class="pl-s"&gt;'37.0167'&lt;/span&gt;;
  &lt;span class="pl-c1"&gt;--longitude&lt;/span&gt;: &lt;span class="pl-s"&gt;'-122.0024'&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can make use of these properties in an HTML document like so:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;link&lt;/span&gt; &lt;span class="pl-c1"&gt;rel&lt;/span&gt;="&lt;span class="pl-s"&gt;stylesheet&lt;/span&gt;" &lt;span class="pl-c1"&gt;href&lt;/span&gt;="&lt;span class="pl-s"&gt;https://latest-with-plugins.datasette.io/fixtures/roadside_attractions.css&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;style&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
.&lt;span class="pl-c1"&gt;attraction-name&lt;/span&gt;:&lt;span class="pl-c1"&gt;after&lt;/span&gt; { &lt;span class="pl-c1"&gt;content&lt;/span&gt;: &lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--name&lt;/span&gt;); }
.&lt;span class="pl-c1"&gt;attraction-address&lt;/span&gt;:&lt;span class="pl-c1"&gt;after&lt;/span&gt; { &lt;span class="pl-c1"&gt;content&lt;/span&gt;: &lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--address&lt;/span&gt;); }
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;style&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;p&lt;/span&gt; &lt;span class="pl-c1"&gt;class&lt;/span&gt;="&lt;span class="pl-s"&gt;attraction-name&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;Attraction name: &lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;p&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;p&lt;/span&gt; &lt;span class="pl-c1"&gt;class&lt;/span&gt;="&lt;span class="pl-s"&gt;attraction-address&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;Address: &lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;p&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here that is &lt;a href="https://codepen.io/simonwillison/pen/MWjXRdP"&gt;on CodePen&lt;/a&gt;. It outputs this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Attraction name: The Mystery Spot&lt;/p&gt;
&lt;p&gt;Address: 465 Mystery Spot Road, Santa Cruz, CA 95065&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Apparently modern screen readers will read these values, so they're at least somewhat accessible. Sadly users won't be able to copy and paste their values.&lt;/p&gt;
&lt;p&gt;Let's try something more fun: a stylesheet that changes colour based on the time of the day.&lt;/p&gt;
&lt;p&gt;I'm in San Francisco, which is currently 8 hours off UTC. So &lt;a href="https://latest-with-plugins.datasette.io/fixtures?sql=select+strftime%28%27%25H%27%2C+%27now%27%29+-+8"&gt;this SQL query&lt;/a&gt; gives me the current hour of the day in my timezone:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;SELECT&lt;/span&gt; strftime(&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;%H&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;now&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;) &lt;span class="pl-k"&gt;-&lt;/span&gt; &lt;span class="pl-c1"&gt;8&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I'm going to define the following sequence of colours:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Midnight to 4am: black&lt;/li&gt;
&lt;li&gt;4am to 8am: grey&lt;/li&gt;
&lt;li&gt;8am to 4pm: yellow&lt;/li&gt;
&lt;li&gt;4pm to 6pm: orange&lt;/li&gt;
&lt;li&gt;6pm to midnight: black again&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's a SQL query for that, using the &lt;code&gt;CASE&lt;/code&gt; expression:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;SELECT&lt;/span&gt;
  CASE
    WHEN strftime(&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;%H&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;now&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;) &lt;span class="pl-k"&gt;-&lt;/span&gt; &lt;span class="pl-c1"&gt;8&lt;/span&gt; BETWEEN &lt;span class="pl-c1"&gt;4&lt;/span&gt;
    &lt;span class="pl-k"&gt;AND&lt;/span&gt; &lt;span class="pl-c1"&gt;7&lt;/span&gt; THEN &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;grey&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
    WHEN strftime(&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;%H&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;now&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;) &lt;span class="pl-k"&gt;-&lt;/span&gt; &lt;span class="pl-c1"&gt;8&lt;/span&gt; BETWEEN &lt;span class="pl-c1"&gt;8&lt;/span&gt;
    &lt;span class="pl-k"&gt;AND&lt;/span&gt; &lt;span class="pl-c1"&gt;15&lt;/span&gt; THEN &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;yellow&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
    WHEN strftime(&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;%H&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;now&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;) &lt;span class="pl-k"&gt;-&lt;/span&gt; &lt;span class="pl-c1"&gt;8&lt;/span&gt; BETWEEN &lt;span class="pl-c1"&gt;16&lt;/span&gt;
    &lt;span class="pl-k"&gt;AND&lt;/span&gt; &lt;span class="pl-c1"&gt;18&lt;/span&gt; THEN &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;orange&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
    ELSE &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;black&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
  END &lt;span class="pl-k"&gt;as&lt;/span&gt; [&lt;span class="pl-k"&gt;time&lt;/span&gt;&lt;span class="pl-k"&gt;-&lt;/span&gt;of&lt;span class="pl-k"&gt;-&lt;/span&gt;day&lt;span class="pl-k"&gt;-&lt;/span&gt;color]&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Execute that &lt;a href="https://latest-with-plugins.datasette.io/fixtures?sql=SELECT%0D%0A++CASE%0D%0A++++WHEN+strftime%28%27%25H%27%2C+%27now%27%29+-+8+BETWEEN+4%0D%0A++++AND+7+THEN+%27grey%27%0D%0A++++WHEN+strftime%28%27%25H%27%2C+%27now%27%29+-+8+BETWEEN+8%0D%0A++++AND+15+THEN+%27yellow%27%0D%0A++++WHEN+strftime%28%27%25H%27%2C+%27now%27%29+-+8+BETWEEN+16%0D%0A++++AND+18+THEN+%27orange%27%0D%0A++++ELSE+%27black%27%0D%0A++END+as+%5Btime-of-day-color%5D"&gt;here&lt;/a&gt;, then add the &lt;code&gt;.css&lt;/code&gt; extension and &lt;a href="https://latest-with-plugins.datasette.io/fixtures.css?sql=SELECT%0D%0A++CASE%0D%0A++++WHEN+strftime(%27%25H%27,+%27now%27)+-+8+BETWEEN+4%0D%0A++++AND+7+THEN+%27grey%27%0D%0A++++WHEN+strftime(%27%25H%27,+%27now%27)+-+8+BETWEEN+8%0D%0A++++AND+15+THEN+%27yellow%27%0D%0A++++WHEN+strftime(%27%25H%27,+%27now%27)+-+8+BETWEEN+16%0D%0A++++AND+18+THEN+%27orange%27%0D%0A++++ELSE+%27black%27%0D%0A++END+as+%5Btime-of-day-color%5D"&gt;you get this&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;:root {
  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;--&lt;/span&gt;time-of-day-color: 'yellow';&lt;/span&gt;
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This isn't quite right. The &lt;code&gt;yellow&lt;/code&gt; value is wrapped in single quotes - but that means it won't work as a colour if used like this:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;style&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-ent"&gt;nav&lt;/span&gt; {
  &lt;span class="pl-c1"&gt;background-color&lt;/span&gt;: &lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--time-of-day-color&lt;/span&gt;);
}
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;style&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;nav&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;This is the navigation&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;nav&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To fix this, &lt;code&gt;datasette-css-properties&lt;/code&gt; supports a &lt;code&gt;?_raw=&lt;/code&gt; querystring argument for specifying that a specific named column should not be quoted, but should be returned as the exact value that came out of the database.&lt;/p&gt;
&lt;p&gt;So we add &lt;code&gt;?_raw=time-of-day-color&lt;/code&gt; to the URL &lt;a href="https://latest-with-plugins.datasette.io/fixtures.css?sql=SELECT%0D%0A++CASE%0D%0A++++WHEN+strftime(%27%25H%27,+%27now%27)+-+8+BETWEEN+4%0D%0A++++AND+7+THEN+%27grey%27%0D%0A++++WHEN+strftime(%27%25H%27,+%27now%27)+-+8+BETWEEN+8%0D%0A++++AND+15+THEN+%27yellow%27%0D%0A++++WHEN+strftime(%27%25H%27,+%27now%27)+-+8+BETWEEN+16%0D%0A++++AND+18+THEN+%27orange%27%0D%0A++++ELSE+%27black%27%0D%0A++END+as+%5Btime-of-day-color%5D&amp;amp;_raw=time-of-day-color"&gt;to get this&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-css"&gt;&lt;pre&gt;:&lt;span class="pl-c1"&gt;root&lt;/span&gt; {
  &lt;span class="pl-c1"&gt;--time-of-day-color&lt;/span&gt;: yellow;
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(I'm a little nervous about the &lt;code&gt;_raw=&lt;/code&gt; feature. It &lt;em&gt;feels&lt;/em&gt; like it could be a security hole, potentially as an XSS vector. I have an &lt;a href="https://github.com/simonw/datasette-css-properties/issues/1"&gt;open issue about that&lt;/a&gt; and I'd love to get some feedback - I'm serving the page with the &lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt; HTTP header which I think should keep things secure but I'm worried there may be attack patterns that I don't know about.)&lt;/p&gt;
&lt;p&gt;Let's take a moment to admire the full HTML document for &lt;a href="https://codepen.io/simonwillison/pen/wvzXZLr"&gt;this demo&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;link&lt;/span&gt; &lt;span class="pl-c1"&gt;rel&lt;/span&gt;="&lt;span class="pl-s"&gt;stylesheet&lt;/span&gt;" &lt;span class="pl-c1"&gt;href&lt;/span&gt;="&lt;span class="pl-s"&gt;https://latest-with-plugins.datasette.io/fixtures.css?sql=SELECT%0D%0A++CASE%0D%0A++++WHEN+strftime(%27%25H%27,+%27now%27)+-+8+BETWEEN+4%0D%0A++++AND+7+THEN+%27grey%27%0D%0A++++WHEN+strftime(%27%25H%27,+%27now%27)+-+8+BETWEEN+8%0D%0A++++AND+15+THEN+%27yellow%27%0D%0A++++WHEN+strftime(%27%25H%27,+%27now%27)+-+8+BETWEEN+16%0D%0A++++AND+18+THEN+%27orange%27%0D%0A++++ELSE+%27black%27%0D%0A++END+as+%5Btime-of-day-color%5D&amp;amp;_raw=time-of-day-color&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;style&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-ent"&gt;nav&lt;/span&gt; {
  &lt;span class="pl-c1"&gt;background-color&lt;/span&gt;: &lt;span class="pl-en"&gt;var&lt;/span&gt;(&lt;span class="pl-s1"&gt;--time-of-day-color&lt;/span&gt;);
}
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;style&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;nav&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;This is the navigation&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;nav&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's a SQL query URL-encoded into the querystring for a stylesheet, loaded in a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; element and used to style an element on a page. It's calling and reacting to an API with not a line of JavaScript required!&lt;/p&gt;
&lt;p&gt;Is this plugin useful for anyone? Probably not, but it's a really fun idea, and it's a great illustration of how having plugins dramatically reduces the friction against trying things like this out.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/apis"&gt;apis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css"&gt;css&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css-custom-properties"&gt;css-custom-properties&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="apis"/><category term="css"/><category term="projects"/><category term="datasette"/><category term="css-custom-properties"/></entry><entry><title>datasette-css-properties</title><link href="https://simonwillison.net/2021/Jan/7/datasette-css-properties/#atom-tag" rel="alternate"/><published>2021-01-07T19:42:37+00:00</published><updated>2021-01-07T19:42:37+00:00</updated><id>https://simonwillison.net/2021/Jan/7/datasette-css-properties/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://datasette.io/plugins/datasette-css-properties"&gt;datasette-css-properties&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
My new Datasette plugin defines a “.css” output format which returns the data from the query as a valid CSS stylesheet defining custom properties for each returned column. This means you can build a page using just HTML and CSS that consumes API data from Datasette, no JavaScript required! Whether this is a good idea or not is left as an exercise for the reader.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/simonw/status/1346528420222488578"&gt;@simonw&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/css"&gt;css&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css-custom-properties"&gt;css-custom-properties&lt;/a&gt;&lt;/p&gt;



</summary><category term="css"/><category term="projects"/><category term="datasette"/><category term="css-custom-properties"/></entry><entry><title>Custom Properties as State</title><link href="https://simonwillison.net/2021/Jan/7/custom-properties-state/#atom-tag" rel="alternate"/><published>2021-01-07T19:39:49+00:00</published><updated>2021-01-07T19:39:49+00:00</updated><id>https://simonwillison.net/2021/Jan/7/custom-properties-state/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://css-tricks.com/custom-properties-as-state/"&gt;Custom Properties as State&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Fascinating thought experiment by Chris Coyier: since CSS custom properties can be defined in an external stylesheet, we can APIs that return stylesheets defining dynamically server-side generated CSS values for things like time-of-day colour schemes or even strings that can be inserted using &lt;code&gt;::after { content: var(--my-property)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This gave me a very eccentric idea for &lt;a href="https://datasette.io/plugins/datasette-css-properties"&gt;a Datasette plugin&lt;/a&gt;...


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/apis"&gt;apis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css"&gt;css&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css-custom-properties"&gt;css-custom-properties&lt;/a&gt;&lt;/p&gt;



</summary><category term="apis"/><category term="css"/><category term="css-custom-properties"/></entry><entry><title>Why "variables" in CSS are harmful</title><link href="https://simonwillison.net/2008/Aug/6/cssvariables/#atom-tag" rel="alternate"/><published>2008-08-06T00:13:55+00:00</published><updated>2008-08-06T00:13:55+00:00</updated><id>https://simonwillison.net/2008/Aug/6/cssvariables/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.w3.org/People/Bos/CSS-variables"&gt;Why &amp;quot;variables&amp;quot; in CSS are harmful&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Bert Bos thinks constants or macros in CSS will make it harder to learn. I personally think that the problem with CSS isn’t the learning curve, it’s how difficult it is to maintain later—and I see macros as a great way of reducing that maintainability burden.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/bert-bos"&gt;bert-bos&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css"&gt;css&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cssconstants"&gt;cssconstants&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/maintainability"&gt;maintainability&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css-custom-properties"&gt;css-custom-properties&lt;/a&gt;&lt;/p&gt;



</summary><category term="bert-bos"/><category term="css"/><category term="cssconstants"/><category term="maintainability"/><category term="css-custom-properties"/></entry><entry><title>CSS Variables</title><link href="https://simonwillison.net/2008/Apr/25/css/#atom-tag" rel="alternate"/><published>2008-04-25T23:26:46+00:00</published><updated>2008-04-25T23:26:46+00:00</updated><id>https://simonwillison.net/2008/Apr/25/css/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://disruptive-innovations.com/zoo/cssvariables/"&gt;CSS Variables&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Hooray! My number one requested CSS feature (and I know I’m not alone), proposed by Daniel Glazman and David Hyatt so I imagine we’ll see it trialled in WebKit pretty soon.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/css"&gt;css&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/daniel-glazman"&gt;daniel-glazman&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/david-hyatt"&gt;david-hyatt&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/variables"&gt;variables&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webkit"&gt;webkit&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css-custom-properties"&gt;css-custom-properties&lt;/a&gt;&lt;/p&gt;



</summary><category term="css"/><category term="daniel-glazman"/><category term="david-hyatt"/><category term="variables"/><category term="webkit"/><category term="css-custom-properties"/></entry></feed>