Future Scenarios Generator - Third Wave
A slot machine for speculation. Enter a topic and get a near-future scenario on that topic generated automatically.
A slot machine for speculation. Enter a topic and get a near-future scenario on that topic generated automatically.
A thorough deep dive into generated content in CSS.
It’s been a busy two weeks of travelling and speaking. Last week I spoke at Finch Conf in Edinburgh, Code Motion in Madrid, and Generate CSS in London. This week I was at Indie Web Camp, View Source, and Fronteers, all in Amsterdam.
The Edinburgh-Madrid-London whirlwind wasn’t ideal. I gave the opening talk at Finch Conf, then immediately jumped in a taxi to get to the airport to fly to Madrid, so I missed all the excellent talks. I had FOMO for a conference I actually spoke at.
I did get to spend some time at Code Motion in Madrid, but that was a waste of time. It was one of those multi-track events where the trade show floor is prioritised over the talks (and the speakers don’t get paid). I gave my talk to a mostly empty room—the classic multi-track experience. On the plus side, I had a wonderful time with Jessica exploring Madrid’s many tapas delights. The food and drink made up for the sub-par conference.
I flew back from Madrid to the UK, and immediately went straight to London to deliver the closing talk of Generate CSS. So once again, I didn’t get to see any of the other talks. That’s a real shame—it sounds like they were all excellent.
The day after Generate though, I took the Eurostar to Amsterdam. That’s where I’ve been ever since. There were just as many events as in the previous week, but because they were all in Amsterdam, I could savour them properly, instead of spending half my time travelling.
Indie Web Camp Amsterdam was excellent, although I missed out on the afternoon discussions on the first day because I popped over to the Mozilla Tech Speakers event happening at the same time. I was there to offer feedback on lightning talks. I really, really enjoyed it.
I’d really like to do more of this kind of thing. There aren’t many activities I feel qualified to give advice on, but public speaking is an exception. I’ve got plenty of experience that I’m eager to share with up-and-coming speakers. Also, I got to see some really great lightning talks!
Then it was time for View Source. There was a mix of talks, panels, and breakout conversation corners. I saw some fantastic talks by people I hadn’t seen speak before: Melanie Richards, Ali Spittal, Sharell Bryant, and Tejas Kumar. I gave the closing keynote, which was warmly received—that’s always very gratifying.
After one day of rest, it was time for Fronteers. This was where myself and Remy gave the joint talk we’ve been working on:
Neither of us is under any illusions about the nature of a joint talk. It’s not half as much work; it’s more like twice the work. We’ve both seen enough uneven joint presentations to know what we want to avoid.
I’m happy to say that it went off without a hitch. Remy definitely had the tougher task—he did a live demo. Needless to say, he did it flawlessly. It’s been a real treat working with Remy on this. Don’t tell him I said this, but he’s kind of a web hero of mine, so this was a real honour and a privilege for me.
I’ve got some more speaking engagements ahead of me. Most of them are in Europe so I’m going to do my utmost to travel to them by train. Flying is usually more convenient but it’s terrible for my carbon footprint. I’m feeling pretty guilty about that Madrid trip; I need to make ammends.
I’ll be travelling to France next week for Paris Web. Taking the Eurostar is a no-brainer for that one. Straight after that Jessica and I will be going to Frankfurt for the book fair. Taking the train from Paris to Frankfurt will be nice and straightforward.
I’ll be back in Brighton for Indie Web Camp on the weekend of October 19th and 20th—you should come!—and then I’ll be heading off to Antwerp for Full Stack Fest. Anywhere in Belgium is easily reachable by train so that’ll be another Eurostar journey.
After that, it gets a little trickier. I’ll be going to Berlin for Beyond Tellerrand but I’m not sure I can make it work by train. Same goes for Web Clerks in Vienna. Cities that far east are tough to get to by train in a reasonable amount of time (although I realise that, compared to many others, I have the luxury of spending time travelling by train).
Then there are the places that I can only get to by plane. There’s the United States. I’ll be speaking at An Event Apart in San Francisco in December. A flight is unavoidable. Last time we went to the States, Jessica and I travelled by ocean liner. But that isn’t any better for the environment, given the low-grade fuel burned by ships.
And then there’s Ireland. I make trips back there to see my mother, but there’s no alternative to flying or taking a ferry—neither are ideal for the environment. At least I can offset the carbon from my flights; the travel equivalent to putting coins in the swear jar.
Don’t get me wrong—I’m not moaning about the amount of travel involved in going to conferences and workshops. It’s fantastic that I get to go to new and interesting places. That’s something I hope I never take for granted. But I can’t ignore the environmental damage I’m doing. I’ll be making more of an effort to travel by train to Europe’s many excellent web events. While I’m at it, I can ask Paul for his trainspotter expertise.
Cassie posted a neat tiny lesson that she’s written a reduced test case for.
Here’s the situation…
CSS custom properties are fantastic. You can drop them in just about anywhere that a property takes a value.
Here’s an example of defining a custom property for a length:
:root {
--my-value: 1em;
}
Then I can use that anywhere I’d normally give something a length:
.my-element {
margin-bottom: var(--my-value);
}
I went a bit overboard with custom properties on the new Patterns Day site. I used them for colour values, font stacks, and spacing. Design tokens, I guess. They really come into their own when you combine them with media queries: you can update the values of the custom properties based on screen size …without having to redefine where those properties are applied. Also, they can be updated via JavaScript so they make for a great common language between CSS and JavaScript: you can define where they’re used in your CSS and then update their values in JavaScript, perhaps in response to user interaction.
But there are a few places where you can’t use custom properties. You can’t, for example, use them as part of a media query. This won’t work:
@media all and (min-width: var(--my-value)) {
...
}
You also can’t use them in generated content if the value is a number. This won’t work:
:root {
--number-value: 15;
}
.my-element::before {
content: var(--number-value);
}
Fair enough. Generated content in CSS is kind of a strange beast. Eric delivered an entire hour-long talk at An Event Apart in Seattle on generated content.
But Cassie found a workaround if the value you want to put into that content
property is numeric. The CSS counter
value is a kind of generated content—the numbers that appear in front of ordered list items. And you can control the value of those numbers from CSS.
CSS counters work kind of like variables. You name them and assign values to them using the counter-reset
property:
.my-element {
counter-reset: mycounter 15;
}
You can then reference the value of mycounter
in a content
property using the counter
value:
.my-element {
content: counter(mycounter);
}
Cassie realised that even though you can’t pass in a custom property directly to generated content, you can pass in a custom property to the counter-reset
property. So you can do this:
:root {
--number-value: 15;
}
.my-element {
counter-reset: mycounter var(--number-value);
content: counter(mycounter);
}
In a roundabout way, this allows you to use a custom property for generated content!
I realise that the use cases are pretty narrow, but I can’t help but be impressed with the thinking behind this. Personally, I would’ve just read that generated content doesn’t accept custom properties and moved on. I would’ve given up quickly. But Cassie took a step back and found a creative pass-the-parcel solution to the problem.
I feel like this is a hack in the best sense of the word: a creatively improvised solution to a problem or limitation.
I was trying to display the numeric value stored in a CSS variable inside generated content… Turns out you can’t do that. But you can do this… codepen.io/cassie-codes/p… (not saying you should, but you could)
It’s time for the afternoon talks at An Event Apart in Seattle. We’re going to have back-to-back CSS, kicking off with Eric Meyer. His talk is called Generation Style. The blurb says:
Consider, if you will, CSS generated content. We can, and sometimes even do, use it to insert icons before or after pieces of text. Occasionally we even use it add a bit of extra information. And once upon a time, we pressed it into service as a hack to get containers to wrap around their floated children. That’s all fine—but what good is generated content, really? What can we do with it? What are its limitations? And how far can we push content generation in a new landscape full of flexible boxes, grids, and more? Join Eric as he turns a spotlight on generated content and shows how it can be a generator of creativity as well as a powerful, practical tool for everyday use.
Wish me luck, ‘cause I’m going to try to capture the sense of this presentation…
So we had a morning of personas and user journeys. This afternoon: code, baby! Eric is going to dive into a very specific corner of CSS—generated content. For an hour. Let’s do it!
He shows the CSS Generated Content Module Level 3. Eric wants to focus on one bit: the pseudo-elements ::before
and ::after
. What does pseudo-element mean?
You might have used one of these pseudo-elements for blockquotes. Perhaps you’ve put a great big quotation mark in front of them.
blockquote:: after {
content: "“";
font-size: 4em;
opacity: 0.67;
/* placement styles here */
}
Why is Eric using ::after
? Because you can. You can put the ::after
content wherever you want. But if your placement styles fail, this isn’t a good place for the generated content. So don’t do this. Use ::before
.
Another example of using generated content is putting icons beside certain links:
a[href$=".pdf"]::after {
content: url(i/icon.png);
height: 1em;
margin-right: 0.5em;
vertical-align: top;
}
But these icons look yucky. But if you use larger images, they will be shown full size. You only have so much control over what happens in there. I mean, that’s true of all CSS: think of CSS as a series of strong suggestions. But here, we have even less control than we’re used to. Why isn’t the image 1em
tall like I’ve specified in the CSS? Well, the generated content box is 1em
tall but the image is breaking out of this box. How about this:
a[href]::after * {
max-width: 100%;
max-height: 100%
}
This doesn’t work. The image isn’t an element so it can’t be selected for.
The way around it is to use background images instead:
a[href$=".pdf"]::after {
content: '';
height: 1em; width: 1em;
margin-right: 0.5em;
vertical-align: top;
background: center/contain;
background-image: url(i/icon.png);
}
Notice there’s a right margin there. That stretches out the width of the whole link. That’s exactly the same as if there were an actual span
in there:
a[href$=".pdf"] span {
height: 1em; width: 1em;
margin-right: 0.5em;
vertical-align: top;
background: center/contain;
background-image: url(i/icon.png);
}
So why use generated content instead of a span
? So that you don’t have to put extra span
s in your markup.
Generated content is great for things that work great when they’re there, but still work fine if they’re not. It’s progressive enhancement.
You’ve almost certainly used generated content for the clearfix hack.
.clearfix::after {
content: '';
display: table;
clear: both;
}
Ask your parents. It’s when we wanted to make the containing element for a group of floating elements to encompass the height of those elements. Ancient history, right? Well, Eric is showing an example of a certain large media company today. There are a lot of clearfixes in there.
Eric makes the clearfix visible:
.clearfix::after {
content: '';
display: table;
clear: both;
border: 10px solid purple;
}
It looks like a span: a 10 pixel wide box. Now change the display
property:
.clearfix::after {
content: '';
display: block;
clear: both;
border: 10px solid purple;
}
Now it behaves more like a div
than a span
.
The big question here is: who cares?
Let’s say we’re making a site about corduroy pillows (I hear they’re really making headlines).
<header>
<h1>Corduroy pillows</h1>
<p>Lorum ipsum...</p>
</header>
We can add a box under the header
:
header::after {
content: " ";
display: block;
height: 1em;
}
You can do stuff with that extra content, like using a linear gradient:
header::after {
content: " ";
display: block;
height: 1em;
background: linear-gradient(to right, #DDD, #000, #DDD) center / 100% 1px no-repeat;
}
The colour stops are #DDD, #000, and #DDD. You get this nice gradiated line under the header
. You can chain a bunch of of radial gradients together to get some nice effects. You could mix in some background images too. Now you’ve got some on-brand separators. You could use generated content to add some “under construction” separators.
By the way, ever struggled to keep track of the order of backgrounds? Think about how you would order layers in Photoshop.
How about if we could use generated content to make design tools?
div[id]::before {
content: attr(id);
}
Now the generated content is taken from the id
attribute. You can make it look like Firebug:
div[id]::before {
content: '#' attr(id);
font: 0.75rem monospace;
position: absolute;
top: 0;
left: 0;
border: 1px dashed red;
padding: 0 0.25em;
background: #FFD;
}
You can even make the content cover the whole box with bottom
and right
values too:
div[id]::before {
content: '#' attr(id);
font: 0.75rem monospace;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
border: 1px dashed red;
padding: 0 0.25em;
background: #FFD8;
}
(And yes, that is a hex value with opacity.)
Let’s make it less code-y:
div[id]::before {
content: attr(id);
font: bold 1.5rem Georgia serif;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
border: 1px dashed red;
padding: 0 0.25em;
background: #FFD8;
}
Throw in some text-shadow
. Maybe some radial gradients. We’re at the wireframe stage. Let’s drop in some SVG images to show lines across the boxes.
How about automating design touches?
pre {
padding: 0.75em 1.5em;
background: #EEE;
font: medium Consolas, monospace;
position: relative;
}
Let’s say that applies to:
<pre class="css">
...
</pre>
You can generate labels with that class attribute:
pre::before {
content: attr(class);
display: block;
padding: 0.25em 0 0.15em;
font: bold 1em Noah, sans-serif;
text-align: center;
text-transform: uppercase;
}
Let’s align it to the top of it’s parent with negative margins:
pre::before {
content: attr(class);
display: block;
padding: 0.25em 0 0.15em;
margin: -0.75em -1.5em 1em;
font: bold 1em Noah, sans-serif;
text-align: center;
text-transform: uppercase;
}
Or you can use absolute positioning:
pre::before {
content: attr(class);
display: block;
padding: 0.25em 0 0.15em;
position: absolute;
top: 0;
right: 0;
left: 0;
font: bold 1em Noah, sans-serif;
text-align: center;
text-transform: uppercase;
}
Now let’s change the writing mode:
pre::before {
content: attr(class);
display: block;
padding: 0.25em 0 0.15em;
position: absolute;
top: 0;
right: 0;
bottom: 0;
writing-mode: vertical-rl;
font: bold 1em Noah, sans-serif;
text-align: center;
text-transform: uppercase;
}
Now the text is running down the side, but it’s turned on its side. You can transform it:
pre::before {
content: attr(class);
display: block;
padding: 0.25em 0 0.15em;
position: absolute;
top: 0;
right: 0;
bottom: 0;
writing-mode: vertical-rl;
transform: rotate(180deg);
font: bold 1em Noah, sans-serif;
text-align: center;
text-transform: uppercase;
}
But if you this, be careful. Your left margin is no longer on the left. Everything’s flipped around.
You could also update the generated content according to the value of the class
attribute:
pre.css:: before {
content: '{ CSS }';
}
pre.html::before {
content: '< HTML >';
}
pre.js::before,
pre.javascript::before {
content: '({ JS })();';
}
It’s presentational, so CSS feels like the right place to do this. But you can’t generate markup—just text. Angle brackets will be displayed in their raw form.
But positioning is so old-school. Let’s use CSS grid:
pre {
display: grid;
grid-template-columns: min-content 1fr;
grid-gap: 0.75em;
}
pre::before {
content: attr(class);
margin: -1em 0;
padding: 0.25em 0.1em 0.25em 0;
writing-mode: vertical-rl;
transform: rotate(180deg);
font: bold 1em Noah, sans-serif;
text-align: center;
text-transform: uppercase;
}
Heck, you could get rid of the negative margins by putting the code content inside a code
element and giving that a margin
of 1em
.
You can see generated content in action on the website of An Event Apart:
li.news::before {
content: attr(data-cat);
background-color: orange;
color: white;
}
The data-cat
attribute (which contains a category value) is displayed in the generated content.
Cool. That’s all stuff we can do now. What about next?
Well, suppose you had to put some legalese on your website. You could generate the numbers of nested sections:
h1 { counter-reset: section; }
h2 { counter-reset: subsection; }
Increment the numbers each time:
h2 { counter-increment: section; }
h3 { counter-increment: subsection; }
And display those values:
h2::before {
content: counter(section) ".";
}
h2::before {
content: counter(section) counter ":" (subsection, upper-roman);
}
Soon you’ll be able to cycle through a list of counter styles of your own creation with a @counter-style
block.
But remember, if you really need that content to be visible for everyone, don’t rely on generated content: put it in your markup. It’s for styles.
So, generated content. It’s pretty cool. You can do some surprising things with it. Maybe ::before
this talk, you didn’t think about generated content much, but ::after
this talk ,you will.
Training a neural network to do front-end development.
I didn’t understand any of this.
I am an artificial intelligence dedicated to generating unlimited amounts of unique inspirational quotes for endless enrichment of pointless human existence.
In July we started receiving audio signals from outside the solar system, and we’ve been studying them since.
Tweets contain sound samples on Soundcloud, data visualisations, and notes about life at the observatory …all generated by code.
ARP is a fictional radio telescope observatory, it’s a Twitter & SoundCloud bot which procedurally generates audio, data-visualisations, and the tweets (and occasionally long-exposure photography) of an astronomer/research scientist who works at ARP, who is obsessive over the audio messages, and who runs the observatory’s Twitter account.
Almost six minutes of me squinting in the sun and sharing my reckons while seagulls squawk in the background.
Wikipedia edits converted into Eno-esque sound.
A fun bit of Markov chaining of your tweets. Some of mine:
Had a burrito in Barcelona. Thank you get the peacocks plumage.
Stand by to the most helpful. The Fuck Was That type shop and David Byrne walked into a Wikipedia entry?
Last Waltz again. This Is A demonstration of The office doors are they talk right now. Cool your plans.
Picking salad leaves from the people who own them. They’re just resting” at the communal testing lab is!
Heading out the standard option. Alas, there’s no signs of spending Bloomsday as constructive feedback?
Derek hits the nail on the head. User-generated content is such a cold, cold term.