The field-sizing property is coming to Firefox 152, making it available across all major engines. It allows
you to control the sizing behavior of elements with a default preferred size, such as form elements.
…
Sometimes a new CSS feature comes along and I immediately “get it”. Like: that’s a cool new feature, I can already see how it’ll save me time, or make things simpler, or improve
accessibility, or allow me to do something new.
Other times, like this one, I initially shrug. What’s the point?, I think…
…and then later in the very same day find occasion to wish it was already mainstream. Hah!
My 12-year-old was interested in learning some HTML and CSS and making her own website. If she were anybody else I’d point her at something like Nekoweb as a starter host because their web-based (VSCode-based) “Nekode” text editor makes writing your first static site simple.
But I’ve got a NAS sitting at home on a fibre connection, so I figured: I might as well just host something similar here.
Here’s how I did it:
1. DNS
I pointed her domain at my static IP, plus a subdomain for the “backend” interface. Suppose her site would be at example.net (and www.example.net) with the admin interface at
admin.example.net: my DNS configuration might look like this:
The templates directive means that, if/when she wants to, she could use Caddy’s built-in SSI-like
features. Or if she decides someday she’d prefer a static site generator then I can sort her out with shell access or something.
I used the OpenVSCode Server Docker image to provide a browser-based VSCode interface in which she could edit HTML, CSS and
JavaScript and drag-drop files from her local machine. I’m using Unraid on my NAS so I didn’t have to think much about running a new Docker container,
but I guess that if I did then I’d have typed something like:
dockerrun-d\# 7890 is the port on my NAS that I'll proxy Caddy to:-p7890:3000
# /mnt/user/example.net is the path on my NAS;# /example.net is where it'll appear within VSCode:-v"/mnt/user/example.net:/example.net"\# this tells OpenVSCode-Server to mount the directory to begin with:-eOPENVSCODE_SERVER_ROOT=/example.net\gitpod/openvscode-server
Now all I needed to do was point Caddy at it. For the time being I simply restricted access to only “computers on my local LAN”, but it’d be easy enough to add authentication using basic auth and/or client
certificates if she wanted to be able to work on her site from elsewhere:
admin.example.net{
# Restrict access to 192.168.* LAN:@allowed{
remote_ip192.168.0.0/16}
# Proxy permitted folks to the container:handle@allowed{
reverse_proxyhttp://nas:7890}
# Block everybody else:handle{
abort}
}
That’s literally all it took to put together a web-based editing environment that publishes directly to a static website. And because it’s on my own infrastructure, it’d be
trivially easy to modify it in the future if she decided to go in a different direction, e.g. a PHP site, or continuous
deployment from a repo, or static site generation from a shell.
That’s all!
Here’s a test site I threw together using exactly this stack, demonstrating the entirely browser-based editing workflow (not shown is drag-and-drop to upload, but I promise that works
too!):
Well this is a fun (and frustrating!) game. You’ll be presented with 20 (alleged) CSS properties, but some of them… are convincing-looking fakes! You’ve got 10 seconds to identify
whether each is real or not. Every few you get right increases the difficulty level, but also the score potential. How high can you score?
Me? Oh, I kept getting up into the “forbidden” level and then my brain would melt and I’d crash out. Quite proud of my last run, though:
Pushing to the main branch of my GitHub/Codeberg/wherever repo would send a webook to my server.
Upon receiving the webhook, my server would pull the latest changes2.
Using a wildcard certificate, my webserver automatically mounts each project at a subdomain matching its project name3.
Here’s what I came up with:
Step 1: webhook handler
I’m using Caddy as my webserver, because despite its considerable power and versatility it’s a breeze
to set up. To sort wildcard DNS later I’ll want to swap in a custom build, but to get started I just ran apt install caddy. Then I used apt install webhook
to install Adnan Hajdarević’s webhook endpoint, and tied the two together in my Caddyfile:
My static server’s called duckling.danq.me, so you’ll see that turn up a lot in these configs.
Then I created a webhook in a GitHub repository:
I generated a long random string to use as the secret, and kept a copy for later.
When you create a webhook in GitHub it immediately sends a test event, but it doesn’t quite look like a real push event so I pushed an inconsequential change to the repo
to trigger another. Once you’ve got a “real” one sent, you can re-send it via the “Recent Deliveries” tab as many times as you like, to help with testing.
Then, on the server, I checked-out a copy of the code (anonymously: this is a public repository so I don’t need keys to read from it anyway) and set up my /etc/webhook.conf to expect
these calls:
The trigger-rule directives ensure that (a) the secret key is correct (it uses a HMAC hash across the entire JSON request, so it prevents payload tampering too) and
(b) the event only triggers on pushes to the main branch. The execute-command specifies the Bash script I want to run when the webhook is triggered. The
pass-arguments-to-command configuration says to send the repo name on to that script.
Now all I needed to do was write the /var/www/github-push/webhook.sh Bash script so that it pulled the latest copy of the code when triggered:
#!/bin/bashcd/var/www/github-push/$1&&gitpull
I was able to test this by pushing inconsequential changes to my codebase and watching them get replicated down to my webserver. Neat!
Step 2: low-maintenance webserver
After pointing the DNS for *.static.duckling.danq.me at my static server, I set about configuring Caddy to be able to use DNS-01 challenges to get itself wildcard SSL
certificates4.
Caddy can’t do DNS-01 challenges out of the box, so you either need to write your own renewal script or compile Caddy with plugins corresponding to your DNS provider. My domains’ DNS
are managed by a mixture of AWS Route 53, Gandi, and Namecheap, so my xcaddy build step looked like this:
For Gandi and Namecheap I just need a personal access token or API key, respectively, but Route 53’s configuration is slightly more-involved: I needed to create a new user via IAM and
give it permission to write DNS TXT records for the appropriate hosted zone. Fortunately the guide for the
caddy-dns/route53 repo had an almost copy-pastable example.
I added the AWS access key and secret key as environment variables (like this!) into my
/etc/systemd/system/multi-user.target.wants/caddy.service service definition, and then told my Caddyfile to make use of them when renewing the wildcard certificate:
The {http.request.host.labels.4} refers to the fourth part of the domain name, when separated at the dots and counted from the right, so 0 = me, 1 =
danq, 2 = duckling, 3 = static, and 4 = the part that we’re interested in. So long as I don’t store any other directories in the
/var/www/github-push/ directory then this will simply map each subdomain onto its git repository name and return a 404 for any other request.
DNS-01 challenges are necessarily slower than HTTP-01/ALPN challenges, because they’re limited by DNS propogation, so it took a while before the
certificate was issued. I ran Caddy in the foreground to watch the logs while it did so:
I don’t yet know if this is going to be the future forever-home of my many static site side projects, but it’s certainly been the most-satisfying experiment to run so-far.
Footnotes
1 I’ve drifted away from selfhosting simple static sites lately because I’ve accidentally
broken them with configuration changes too many times! But I figured I’d be open to in-housing them again if I had a single simple architecture for them all, so I spun up a VPS and
gave it a go
2 Running a build script or some other static site generation tool is out of scope for
now, but I want to be able to confirm that it would be possible in the future.
3 It also needs to be possible for me to map other domain names to it, but that’s
a triviality.
4 It’s absolutely
possible to use tls { on_demand } to do this, but it’s better to use a wildcard certificate which can be pre-generated and doesn’t let people trick your
server into making ludicrous numbers of certificate requests by hammering random subdomain names.
Honestly I just wanted to play around with gradients. But gradients without anything on the horizon lack something, so I added horses. Since I can’t draw horses, now you
can draw them. And watch them parade across the screen alongside horses drawn by people you probably wouldn’t like. Or maybe you would, how should I know?!
…
I love a good (by which I mean stupid) use of a .horse domain name. I’m not sure anything will ever beat endless.horse, but gradient.horse might be a close second.
Draw a horse. Watch it get animated and run wild and free with the horses that other people have drawn. That is all.
Welcome to my 88×31 button creator, this is a pretty rough and ready implementation so it could be buggy, please let me know if you find any issues.
This supports gif despite the basic canvas tag limitation courtesy of gif.js – none of
this would be possible without that project.
…
Dan (whose website is freakin’ awesome, by the way) has done an amazing job with this new 88×31 generator. Look at this (trashy, but I don’t care) button I threw together in literally
seconds, with it:
Have a play, and remind yourself that the Web is brilliant.
What about when you want to keep global styles out of your component, like with a third-party widget that gets loaded on lots of different pages?
I kind-of sort-of see the logic in that. But I also think wanting your component to not look like a cohesive part of the page its loaded into is weird and unexpected.
…
I so-rarely disagree with Chris on JavaScript issues, but I think I kinda do on this one. I fully agree that the Shadow DOM is usually a bad idea and its
encapsulation concept encourages exactly the kind of over-narrow componentised design thinking that React also suffers from. But I think that the rebuttal Chris picks up
on is valid… just sometimes.
When I created the Beige Buttons component earlier this year, I used the shadow DOM. It was the
first time I’ve done so: I’ve always rejected it in my previous (HTML) Web Components for exactly the reasons Chris describes. But I
maintain that it was, in this case, the right tool for the job. The Beige Buttons aren’t intended to integrate into the design of the site on which they’re placed, and
allowing the site’s CSS to interact with some parts of it – such as the “reset” button – could fundamentally undermine the experience it intends to create!
I appreciate that this is an edge case, for sure, and most Web Component libraries almost certainly shouldn’t use the shadow DOM. But I don’t think it’s valid to
declare it totally worthless.
That said, I’ve not yet had the opportunity to play with Cascade Layers, which – combined with directives like all: reset;, might provide a way to strongly
override the style of components without making it impossibly hard for a site owner to provide their own customised experience. I’m still open to persuasion!
Modern CSS is freakin’ amazing. Widespread support for nesting, variables, :has, and :not has unlocked so much potential. But I don’t yet see it used widely
enough.
Suppose I have a form where I’m expecting, but not requiring, a user to choose an option from each of several drop-downs. I want to make it more visually-obvious
which drop-downs haven’t yet had an option selected. Something like this:
It’s a slightly gnarly selector, but thanks to nesting you could choose to break it into multiple blocks if you preferred.
What that’s saying is:
a <select>
that contains an <option>
where that <option> does not have a value="..."
and that <option> is currently selected
gains a dotted red outline around it
Or in short: if the default option is selected, highlight it so the user knows they haven’t chosen a value yet. Sweet!
Obviously you could expand this to have different effects for every value, if you wanted.
I can’t understate how valuable it is that we can do this in CSS, nowadays. Compared to doing it in JavaScript… CSS gives better performance and reliability and is much easier to
implement in a progressively-enhanced manner.
Here’s another example, this time using a fun “dress-up Dan” feature I from a redesign of my blog theme that I’m hoping to launch in the New Year:
If you’ve ever wanted to know what I’d look like if I were an elderly Tom Scott, my new design will answer that question!
Every single bit of interactivity shown in the video above… from the “waving” Dan to the popup menu to the emoji-styled checkboxes to the changes to t-shirt and hair
colours… is implemented in CSS.
The underlying HTML is all semantic, e.g. the drop-down menu is a <details>/<summary> pair (with thanks to Eevee for
the inspiration); its contents are checkbox and radiobutton <input>es; the images are SVGs that use CSS variables (another killer feature these years!) to specify
colours (among other things), and virtually everything else… is CSS.
Consider this:
:root{
/* Default values for Dan's t-shirt, hair, and beard colours used throughout the site: */--dan-tshirt:#c3d4d7;
--dan-hair:#3b6f8f;
--dan-beard:#896a51;
/* ...more variables... */
}
/* When the page contains a "checked" checkbox, update some variables: */
:root:has(#dan-tshirt-color-white:checked){--dan-tshirt:#c3d4d7;}
:root:has(#dan-tshirt-color-purple:checked){--dan-tshirt:#7429a8;}
/* ... */
:root:has(#dan-hair-color-natural:checked){--dan-hair:#896a51;}
:root:has(#dan-hair-color-blue:checked){--dan-hair:#3b6f8f;}
/* When "dye beard" is checked, copy the hair colour: */
:root:has(#dan-dye-beard-toggle:checked){--dan-beard:var(--dan-hair);}
The ability to set :root CSS variables, based on the status of user-controlled elements like checkboxes within the document, unlocks amazing options for interactivity. It
also works in smaller scopes like HTML Web Components, of course, for encapsulated functionality.
If you’re still using JavaScript for things like this, perhaps it’s time you looked at how much CSS has grown up this last decade or so. CSS gives you performance benefits, less
fragility, and makes it easier for you to meet your accessibility and usability goals.
You can still enrich what you create with JavaScript if you like (I’ve got a few lines of JS that save those checkbox states to localStorage so they persist
through page loads, for example).
But a CSS-based approach moves more of your functionality from the “nice to have” to “core” column. And that’s something we can all get behind, right?
An additional thing I wanted to implement – again, for the next version of my blog’s theme – was an “alt text viewer”. Mastodon has one, and it’s excellent2.
Mastodon’s viewer requires JavaScript, but I was inspired when I saw James come up with a
CSS-based version that used a re-styled checkbox.
But I wanted to do one better. Displaying alt text, too, seems like an example of what would semantically be best-represented by a
<details>/<summary> pair.
Clicking on the image shows a larger version in a lightbox; clicking on the ‘alt’ button shows the alt text… all in semantic HTML and vanilla CSS.3
My first attempt tried to put the alt-text widget inside the <summary> of the original image, but that’s an accessibility no-no, so instead I
wrap both<details> blocks (the lightbox, and the alt-text revealer) inside a container and then reposition the latter over the former.
The rest is all the same kinds of tricks I demonstrated previously, to ensure that you can click in-and-out of both in an intuitive way and that keyboard navigation works as you’d
expect.
I can’t use it on my blog yet (because if I do, it’ll probably break horribly when I add the functionality to my entire theme, later!), but I’ve put together a demonstration page that showcases the technique, plus a GitHub repo with all of the code (which is all public domain/unlicensed). Go have a
play and tell me what you think!
Footnotes
1 As a secondary goal, using <details>/<summary>
means that it’ll behave better when CSS is disabled or unavailable, which’ll make it easier to celebrate Naked CSS Day!
2 Why would I, a sighted person, need an alt text viewer, you ask? All kinds of reasons.
Good alt text is for everybody, and can help by providing context, e.g. “explaining” the joke or identifying the probably-well-known-but-I-didn’t-recognise-them subject of a
photo. Here’s some more reasons.
3 If you love owls and you love accessibility, this is the kind of example you should give
a hoot about.
This weekend, I received my copy of DOCTYPE, and man: it feels like a step back to yesteryear to type in a computer program from a
magazine: I can’t have done that in at least thirty years.
So yeah, DOCTYPE is a dead-tree (only) medium magazine containing the source code to 10 Web pages which, when typed-in to your computer, each provide you with some kind of fun and
interactive plaything. Each of the programs is contributed by a different author, including several I follow and one or two whom I’m corresponded with at some point or another, and each
brings their own personality and imagination to their contribution.
I opted to start with Stuart Langridge‘s The Nine Pyramids, a puzzle game about trying to connect all nodes in a 3×3 grid in a
continuous line bridging adjacent (orthogonal or diagonal) nodes without visiting the same node twice nor moving in the same direction twice in a row (that last provision is described
as “not visiting three in a straight line”, but I think my interpretation would have resulted in simpler code: I might demonstrate this, down the line!).
The puzzle actually made me stop to think about it for a bit, which was unexpected and pleasing!
Per tradition with this kind of programming, I made a couple of typos, the worst of which was missing an entire parameter in a CSS conic-gradient() which resulted in the
majority of the user interface being invisible: whoops! I found myself reminded of typing-in the code for Werewolves and
Wanderer from The Amazing Amstrad Omnibus, whose data section – the part most-liable to be affected by a typographic bug without introducing a syntax error – had
a helpful “checksum” to identify if a problem had occurred, and wishing that such a thing had been possible here!
But thankfully a tiny bit of poking in my browser’s inspector revealed the troublesome CSS and I was able to complete the code, and then the puzzle.
I’ve really been enjoying DOCTYPE, and you can still buy a copy if you’d like one of your own. It manages to simultaneously feel both fresh and nostalgic,
and that’s really cool.
About twenty years ago, after a a tumultuouslife, Big.McLargeHuge – the shared server of several other Abnibbers and I – finally and fatally kicked the bucket. I spun up its replacement, New.McLargeHuge, on hosting company DreamHost, and this blog (and many other sites) moved over to it1.
Wow, I’d forgotten half of these websites existed.
I only stayed with DreamHost for a few years before switching to Bytemark, with whom I was a loyal customer right up until a few years
ago2, but in that time I took advantage of DreamHost’s “Refer & Earn” program, which
allowed me to create referral codes that, if redeemed by others who went on to become paying customers, would siphon off a fraction of the profits as a “kickback” against my server
bills. Neat!3
DreamHost’s referrals had a certain “pyramid scheme” feel in that you could get credit for the people referred by the people you referred.
A year or so after I switched to ByteMark, DreamHost decided I owed them money: probably because of a
“quirk” in their systems. I disagreed with their analysis, so I ignored their request. They “suspended” my account (which I wasn’t using anyway), and that was the end of it.
Right?
But the referral fees continued to trickle in. For the last seventeen years, I’ve received a monthly email advising me that my account had been credited, off the back of a
referral.
I have no explanation as to why the amount of the referral reward fluctuates, but I can only assume that it’s the result of different people on different payment schedules?
About once a year I log in and check the balance. I was quite excited to discover that, at current rates, they’d consider me “paid-up” for my (alleged) debt by around Spring 2026!
I had this whole plan that I’d write a blog post about it when the time came. It could’ve been funny!
But it’s not to be: DreamHost emailed me last night to tell me that they’re killing their “Refer & Earn” program; replacing it with something different-but-incompatible (social media’s
already having a grumble about this, I gather).
So I guess this is the only blog post you’ll get about “that time DreamHost decided I owed them money and I opted to pay them back in my referral fees over the course of eighteen
years”.
No big loss.
Footnotes
1 At about the same time I moved Three
Rings over from its previous host, Easily, to DreamHost too, in order to minimise the number of systems I had to keep an eye on. Oh, how different things are now, when I’ve
got servers and domain registrations and DNS providers all over the damn place!
2 Bytemark have rapidly gone downhill since their acquisition by Iomart a while back, IMHO.
3 Nowadays, this blog (and several of my other projects) is hosted by Linode, whose acquisition by Akamai seems not to have caused any problems with, so that’s fab.
Back before PCs were black, they were beige. And even further back, they’d have not only “Reset” and “Power” buttons, but also a “Turbo” button.
I’m not here to tell you what it did1. No, I’m here to show you how to re-live
those glory days with a Turbo button of your very own, implemented as a reusable Web Component that you can install on your very own website:
Go on, press the Turbo button and see what happens.
(Don’t press the Reset button; other people are using this website!)
If you’d like some beige buttons of your own, you can get them at Beige-Buttons.DanQ.dev. Two lines of code and you can
pop them on any website you like. Also, it’s open-source under the Unlicense so you can take it, break it, or do what you like with it.
I’ve been slumming it in some Web Revivalist circles lately, and it might show. Best Resolution (with all its 88×31s2),
which I launched last month, for example.
You might anticipate seeing more retro fun-and-weird going on here. You might be right.
2 I guess that’s another “if you know, you know”, but at least you’ll get fewer
conflicting answers if you search for an explanation than you will if you try to understand the turbo button.
It’s not often these days that I have the experience of “I didn’t know the Web could do that‽”
Once was when I saw DHTML Lemmings, for example, at a time when adding JavaScript to a page usually
meant you were doing nothing more-sophisticated than adding a tooltip/popover or switching the images in your mystery
meat navigation menu. Another was when I saw Google Earth’s browser-based implementation for the first time, performing 3D manipulations of a quality that I’d
previously only seen in dedicated applications.
But I got that today when I played with Layoutit! Terra (from the folks behind one of the better CSS grid layout generators). It’d be
pretty cool if it were “just” a Transport Tycoon-like landscape generator and editor, but the thing that blew my mind was discovered that it’s implemented entirely in HTML and CSS… not
a line of JavaScript to be seen. Even speaking as somebody who played… and then reverse-engineered… things like Blackle Mori’s CSS Puzzle
Box, I can’t even begin to fathom how I’d begin to conceive of such a thing, let alone implement it.
Way back in the day, websites sometimes had banners or buttons (often 88×31 pixels, for complicated historical reasons) to indicate what screen
resolution would be the optimal way to view the site. Just occasionally, you still see these today.
Folks who were ahead of the curve on what we’d now call “responsive design” would sometimes proudly show off that you could use any resolution, in the same way as they’d
proudly state that you could use any browser1!
I saw a “best viewed at any size” 88×31 button recently, and it got me thinking: could we have a dynamic button that always
shows the user’s current resolution as the “best” resolution. So it’s like a “best viewed at any size” button… except even more because it says “whatever
resolution you’re at… that’s perfect; nice one!”
Anyway, I’ve made a website: best-resolution.danq.dev. If you want a “Looks best at [whatever my visitor’s screen
resolution is]” button, you can get one there.
1 I was usually in the camp that felt that you ought to be able to access my site with any
browser, at any resolution and colour depth, and get an acceptable and satisfactory experience. I guess I still am.
2 If you’re reading this via RSS or have JavaScript disabled then you’ll probably see an
“any size” button, but if you view it on the original page with JavaScript enabled then you should see your current browser inner width and height shown on the button.
This post advocates minimizing dependencies in web pages that you do not directly control. It conflates dependencies during build time and dependencies in the browser. I maintain
that they are essentially the same thing, that both have the same potential problems, and that the solution is the snappy new acronym HtDTY – Host the Damn Thing Yourself.
…
If your resources are large enough to cause a problem if you Host the Damn Things Yourself then consider finding ways to cut back on their size. Or follow my related advice –
HtDToaSYHaBRW IMCYMbT(P)WDWYD : Host the Damn Thing on a Service You Have A Business Relationship With, It May Cost You Money But They (Probably) Won’t Dick With Your Data.
…
Host the Damn Thing Yourself (HtDTY) is an excellent suggestion; I’ve been a huge fan of the philosophy for ages, but I like this acronym. (I wish it was pronounceable, but you can’t
have everything.)
Andrew’s absolutely right, but I’m not even sure he’s expressed all the ways in which he’s right. Here are my reasons to HtDTY, especially for frontend resources:
Security: As Andrew observes, you can’t protect against supply chain attacks if your supply chain wide open to exploitation. And I’m glad that he points out that
version pinning doesn’t protect you from this (although subsource integrity can).
Privacy: Similarly, Andrew nailed this one. If you host your fonts on Google Fonts, for example, you’re telling one of the biggest data-harvesting companies on the
Internet who’s accessing your website. Don’t do that (in that specific example, google-webfonts-helper is your friend).
Performance: Andrew rightly deconstructs the outdated argument that CDN caching improves your site’s performance. Edge caching might, in some
circumstances, but still has the problems listed above. But this argument can go further than Andrew’s observation that CDNs aren’t that much of a benefit… because sticking to just
one domain name means (a) fewer DNS lookups, (b) fewer TLS handshakes, (c) better compression, if e.g. your JavaScript assets are bundled or at least delivered in the same pipeline,
and (d) all the benefits of HTTP/2 and HTTP/3, like early hints, pipelining, etc. Nowadays, it can often be faster to not-use a CDN (depending on lots of factors), in
addition to all the above benefits.