# data[].attributes.blurb.html
Niels has shown some of the most amazing web API with hardware demos over the years at HalfStack. He’s back again this year to time travel back to 1994 and rediscover one of the most important principles of the web.

# data[].attributes.slidedeck.data[].slides[].desc.html
I’m Niels. It’s great to be back again in Phoenix. As you can probably tell from my wooden shoes, I am not from the United States. I am from The Netherlands. And no, we don’t actually wear wooden shoes, that is just a stereo type. Mostly. So I am Dutch… I’m a Dutch Bro.

# data[].attributes.slidedeck.data[].slides[].desc.html
And imagine my surprise when I saw this coffee shop across the street. Thanks for making me feel at home. The windmill is also a bit of a stereotype… but…

# data[].attributes.slidedeck.data[].slides[].desc.html
It’s true, we have a lot of windmills in The Netherlands. There were many, many more, but some 1200 historic windmills survive.

# data[].attributes.slidedeck.data[].slides[].desc.html
And many of them were build for water management.

# data[].attributes.slidedeck.data[].slides[].desc.html
We’d build dykes and then use the windmills to pump water out into the canals, creating new dry land.

# data[].attributes.slidedeck.data[].slides[].desc.html
Lots of canals… So if you ever need a canal? Click Some say we have the best canals…. Moving on….

# data[].attributes.slidedeck.data[].slides[].desc.html
I’m a Front-end developer. 30 years and counting. My first official job title was…

# data[].attributes.slidedeck.data[].slides[].desc.html
Webmaster. This is me back in 1994, working on my first website. As you can see I am holding the power sword which gives you the ability to write flawless HTML and defeat Skeletor, the lord of the JavaScripts.

# data[].attributes.slidedeck.data[].slides[].desc.html
If I counted correctly, this is the 13th time I am giving a talk at HalfStack. I just love the mix of web development topics and to see people use browser APIs and Javascript in creative and unexpected ways.

# data[].attributes.slidedeck.data[].slides[].desc.html
So… now the awkward thing… I know this is technically a JavaScript conference…

# data[].attributes.slidedeck.data[].slides[].desc.html
Except my talk will have no Javascript. No React. No Node. Not even Deno and Typescript. No script at all. Except for noscript.

# data[].attributes.slidedeck.data[].slides[].desc.html
The noscript tag.

# data[].attributes.slidedeck.data[].slides[].desc.html
For this talk we’re going back to basics and back in time. We’re going to ask the important questions, like…

# data[].attributes.slidedeck.data[].slides[].desc.html
Can you nest noscript tags?

# data[].attributes.slidedeck.data[].slides[].desc.html
Can you style a noscript tag?

# data[].attributes.slidedeck.data[].slides[].desc.html
And what happens when you put a script tag in a noscript tag?

# data[].attributes.slidedeck.data[].slides[].desc.html
And we’re going to talk about the one thing that HTML is…. that JavaScript simply is not. Whenever you make a mistake in Javascript something bad will happen. One typo and your build will fail. Or worse your browser will… Explode!. Really. True story.

# data[].attributes.slidedeck.data[].slides[].desc.html
But not HTML. Resilience against errors and mistakes is build into the fabric of HTML. You are allowed to make mistakes. In fact you are expected to make mistakes, because HTML was intended to be used by non-professionals. Everybody can make a website.

# data[].attributes.slidedeck.data[].slides[].desc.html
Just read the source code of another website, try it for yourself and call yourself a webmaster. That is what I did. That is what I still do… Except now I ask ChatGPT and Copilot to solve my problems and I call myself a front-end developer. Or when I feel expensive, I am a front-end engineer. But in my heart I am still a webmaster.

# data[].attributes.slidedeck.data[].slides[].desc.html
This is Tim. Tim is a Web Developer. Back in the 80s, he was a engineer at cern - you know the particle accelerator thing in Switzerland - and he wanted to make publishing research and managing information easier allowing scientists to at cern to collaborate with colleagues around the world.

# data[].attributes.slidedeck.data[].slides[].desc.html
So he did what comes naturally to every engineer, come up with overly complicated ways to solve ‘simple’ problems. Let’s build a world wide network of connected information. A world wide web of knowledge if you will. Can’t be too difficult, right? So he wrote a paper “Information Management: A Proposal”.

# data[].attributes.slidedeck.data[].slides[].desc.html
His supervisor - Mike (Sendall) - though it was interesting and wrote this on top of his paper : “vague but exciting”. And that started the whole World Wide Web revolution.

# data[].attributes.slidedeck.data[].slides[].desc.html
He created WorldWideWeb - which was the name of the first browser - which was not just a browser, but also an editor, and created the first website. Not with the vaguest idea that it would lead to the web of today, but to let everybody - anybody - publish information. And it already had the concept of resilience. Sir Tim. The web developer. And yes, that is his real job title.

# data[].attributes.slidedeck.data[].slides[].desc.html
That first website is still here. Still works. After more than 30 years. Any browser will work. This is in my opinion amazing. This truly is resilience.

# data[].attributes.slidedeck.data[].slides[].desc.html
So lets look at the source code. It even looks like HTML, but a bit weird. We can see familiar tags, like body , H1, P and A. But also nextid tag and a title in a header tag. And where is the HTML tag?

# data[].attributes.slidedeck.data[].slides[].desc.html
But it still works. No errors. The page just renders fine.

# data[].attributes.slidedeck.data[].slides[].desc.html
Why does it still work? Browsers will simply “ignore” tags that it does not recognise. This quote is directly from that first website.

# data[].attributes.slidedeck.data[].slides[].desc.html
So a modern browser will see this nextid tag - which it really does not understand… the browser will ignore the tag.

# data[].attributes.slidedeck.data[].slides[].desc.html
Well, ignore is a bit of a strong word. That tag is turned in to an element in the DOM, it’s still there. It’s still a nextid element, but it has no special meaning. It’s just a element, kinda like a regular span element.

# data[].attributes.slidedeck.data[].slides[].desc.html
In fact this is why noscript works.

# data[].attributes.slidedeck.data[].slides[].desc.html
If your browser does not support scripting, it also does not support the noscript tag. It will “ignore” it an turn it into a generic HTML element. Like a span. So the fallback content will be visible.

# data[].attributes.slidedeck.data[].slides[].desc.html
If your browser does support scripting, it also supports the noscript tag. And by “support”, I mean that it actively makes sure that the noscript element and all of its descendant do not show up on screen.

# data[].attributes.slidedeck.data[].slides[].desc.html
This is one of the basic principles of HTML. It ensures forward and backwards compatibility. You could use a browser from 1995, such as Mosaic and load the HalfStack website. It will work. Without any styling of course, because CSS did not exist yet. But still, you can read the text and click links. This is what resilience means.

# data[].attributes.slidedeck.data[].slides[].desc.html
With this mechanism you can do cool stuff, like bring back your favourite tags from the past.

# data[].attributes.slidedeck.data[].slides[].desc.html
Like the blink tag. Yeah, let’s bring back the blink tag.

# data[].attributes.slidedeck.data[].slides[].desc.html
In current browsers the blink tag is just like any other unknown tag. It’s basically a span. So the text will show up on screen, but it is not blinking. This we need to fix.

# data[].attributes.slidedeck.data[].slides[].desc.html
So, just add a little CSS and we can style our blink tag. An infinite animation between two keyframes. Turning on and off the visibility. This will work. We’ve brought back the blink tag. And no I’m not going to demo this. There are limits.

# data[].attributes.slidedeck.data[].slides[].desc.html
And why stop with the blink tag. We can do the same for the marquee tag. It’s a little bit more difficult, but we can get this working. All we need to do is…………. Wait. We don’t need to do anything.

# data[].attributes.slidedeck.data[].slides[].desc.html
Marquee actually still works in all browsers. I didn’t realise this. It’s deprecated by the HTML5 spec, it isn’t actually “baseline”… my apologies to Mariko. But you can actually still use it….. If you wanted to…. Seriously, don’t.

# data[].attributes.slidedeck.data[].slides[].desc.html
Pause What is it that makes HTML so resilient? What makes it so flexible and error friendly? What is it that makes it special?

# data[].attributes.slidedeck.data[].slides[].desc.html
All the magic happens between your HTML source files and the Document Object Model, The DOM.

# data[].attributes.slidedeck.data[].slides[].desc.html
When the browser parses your HTML file, it uses a tokenizer and a tree builder. And those two build the DOM tree that the browser uses to render the page. The browser doesn’t render the HTML, it renders the DOM.

# data[].attributes.slidedeck.data[].slides[].desc.html
The tokeniser.

# data[].attributes.slidedeck.data[].slides[].desc.html
Basically does nothing more than just analyse that HTML file character by character. And it tries to extract all the information it needs to build that DOM tree.

# data[].attributes.slidedeck.data[].slides[].desc.html
So when we have a piece of HTML like this, it will start with in an initial state.

# data[].attributes.slidedeck.data[].slides[].desc.html
The data state. It sees the smaller than sign and thinks this might be a tag. So it goes to the…

# data[].attributes.slidedeck.data[].slides[].desc.html
Tag open state. It sees the letter A and continues with the

# data[].attributes.slidedeck.data[].slides[].desc.html
Tag name state and adds that letter A to the tag name. It then sees a space.

# data[].attributes.slidedeck.data[].slides[].desc.html
And switches state again…

# data[].attributes.slidedeck.data[].slides[].desc.html
And so on… character by character…

# data[].attributes.slidedeck.data[].slides[].desc.html
So now it has all the data about this tag. It knows the tag name, the attribute name and the attribute value.

# data[].attributes.slidedeck.data[].slides[].desc.html
It now has the token -> emit to the tree builder

# data[].attributes.slidedeck.data[].slides[].desc.html
68 different states

# data[].attributes.slidedeck.data[].slides[].desc.html
Some are simple.

# data[].attributes.slidedeck.data[].slides[].desc.html
Most are a bit more complicated.

# data[].attributes.slidedeck.data[].slides[].desc.html
But most importantly. When it encounters a parse error, it will not stop processing the file. It will not show an error message. It will switch to a different state and just continue on.

# data[].attributes.slidedeck.data[].slides[].desc.html
Together, all of these rules determine what HTML looks like. And in this case in particular it determines what tag names are allowed to look like. It determines which characters are allowed an which are not. And just like the rest of HTML, the tokenizer is very forgiving.

# data[].attributes.slidedeck.data[].slides[].desc.html
This is a tag.

# data[].attributes.slidedeck.data[].slides[].desc.html
As long as the first character of the tag name is an ascii letter, we’re good. And this is not an upside down “n”, but a “u”.

# data[].attributes.slidedeck.data[].slides[].desc.html
To be fair, the equals sign is a parse error, but the rules specify exactly what needs to happen in this case. The first equals sign becomes the attribute name.

# data[].attributes.slidedeck.data[].slides[].desc.html
Now I know what you are thinking. This is an accident waiting to happen. But this is why custom elements work.

# data[].attributes.slidedeck.data[].slides[].desc.html
This is why Angular works. Yes, attributes can start with an asterisk. And yes parenthesis in an attribute name is also fine.

# data[].attributes.slidedeck.data[].slides[].desc.html
It’s why Vue works. An attribute that starts with an at sign… no problem.

# data[].attributes.slidedeck.data[].slides[].desc.html
But, yeah… you can go too far. According to the HTML parser spec, this is a valid tag. Not a HTML tag, but a tag nonetheless.

# data[].attributes.slidedeck.data[].slides[].desc.html
This too… Oh…. Eh wait… No this is a mistake. This is obviously…

# data[].attributes.slidedeck.data[].slides[].desc.html
No not a proper tag.

# data[].attributes.slidedeck.data[].slides[].desc.html
Yeah, let’s check the spec. If the tag name contains a space it switches to the before attribute name state.

# data[].attributes.slidedeck.data[].slides[].desc.html
But yeah…..

# data[].attributes.slidedeck.data[].slides[].desc.html
This is not a space. For typographic reasons there are many different spaces of different sizes. All of them white space. But this is not unicode code point 20. This is not a space. This is a figure space.

# data[].attributes.slidedeck.data[].slides[].desc.html
Now, if you really want to mess with your coworker, just run this script on his or her computer. It will replace all spaces between the a and the href to a figure space. In all HTML documents. This will really mess every up.

# data[].attributes.slidedeck.data[].slides[].desc.html
Every document will suddenly be completely screwed up… And the funny part is, when you look at the documents in code editor with a monospaced font, you can’t see the difference…. Pretty evil, right. So actually… don’t do this.

# data[].attributes.slidedeck.data[].slides[].desc.html
Seriously, don’t do this. I’m not responsible for anything that happens.

# data[].attributes.slidedeck.data[].slides[].desc.html
Pause…… So, can you break HTML? Can you put an emoji in an tag name or attribute name and break HTML? Well, no. Not really. This….

# data[].attributes.slidedeck.data[].slides[].desc.html
Is allowed…. Eh… wait….

# data[].attributes.slidedeck.data[].slides[].desc.html
This is allowed…. Yes. Emoji are just unicode and you can put any unicode character in a tag name as long as it starts with a regular letter.

# data[].attributes.slidedeck.data[].slides[].desc.html
And you can style it like this. This works. Just sprinkle a little bit of CSS and voila. Of course nobody serious would want to use emoji’s in production, right…?

# data[].attributes.slidedeck.data[].slides[].desc.html
Anyone remember this?

# data[].attributes.slidedeck.data[].slides[].desc.html
Now I am not saying nothing matters. All tags are equal and semantics are not important…

# data[].attributes.slidedeck.data[].slides[].desc.html
Semantically this is just as bad as…

# data[].attributes.slidedeck.data[].slides[].desc.html
This… These are not a buttons.

# data[].attributes.slidedeck.data[].slides[].desc.html
This is a button. It works as a button. You can tab to it. You can click it. And it’s free.

# data[].attributes.slidedeck.data[].slides[].desc.html
All the tags we created are not HTML tags. They have no function. You can style them, but that is it. Functionality wise they are nothing more than a <span>.

# data[].attributes.slidedeck.data[].slides[].desc.html
The other reason why HTML is so resilient is the tree builder. The tree builder receives the tokens from the tokenizer and actually builds the DOM tree with it. Sounds easy, but it really is more complicated than it seems.

# data[].attributes.slidedeck.data[].slides[].desc.html
So, if we have a piece of HTML like this. A simple a tag with some text in it. The tokenizer will produce three different tokens.

# data[].attributes.slidedeck.data[].slides[].desc.html
The open tag, the character token and the closing tag.

# data[].attributes.slidedeck.data[].slides[].desc.html
And the tree builder turns that into a HTMLAnchorElement DOM node and a text node. Still simple so far. But that is actually not what happens when the tree builder receives these tokens. It does so much more.

# data[].attributes.slidedeck.data[].slides[].desc.html
It actually creates missing elements that are required for your HTML document. Let’s see what happens.

# data[].attributes.slidedeck.data[].slides[].desc.html
So, the first token is a A tag open token, but that does not make sense in this context

# data[].attributes.slidedeck.data[].slides[].desc.html
So it creates the HTMLHtmlElement

# data[].attributes.slidedeck.data[].slides[].desc.html
Then the HTMLHeadElement

# data[].attributes.slidedeck.data[].slides[].desc.html
And a HTMLBodyElement… and now the A tag open finally makes sense.

# data[].attributes.slidedeck.data[].slides[].desc.html
So it creates it….

# data[].attributes.slidedeck.data[].slides[].desc.html
And the text node.

# data[].attributes.slidedeck.data[].slides[].desc.html
And closes the Anchor element.

# data[].attributes.slidedeck.data[].slides[].desc.html
And we’re done. We actually now have a DOM that makes sense and is complete. It creates all the nodes that were missing. And this is why that very first HTML page works, even though it didn’t have an HTML element. Even without that head element it will work just fine. It just re-creates the missing elements.

# data[].attributes.slidedeck.data[].slides[].desc.html
There is now a HTML and head tag. And it move the header tag to the body, because that is where it nowadays makes sense.

# data[].attributes.slidedeck.data[].slides[].desc.html
But it also fixes your mistakes. So if the tree builder encounters some tags that are not nested in the right way, it will try to make sense of it. For example.

# data[].attributes.slidedeck.data[].slides[].desc.html
Everyone makes mistakes. And we don’t want this to happen.

# data[].attributes.slidedeck.data[].slides[].desc.html
We don’t want to go back to the days of xhtml. Where every document had to be very strictly conforming to the spec, otherwise the browser would just give up and show the ugly error message.

# data[].attributes.slidedeck.data[].slides[].desc.html
Given how popular XML was at the time it was understandable that developer wanted to move to XML. But it goes against one of the core tenets of the web. The web is for humans. And humans make mistakes.

# data[].attributes.slidedeck.data[].slides[].desc.html
Fortunately with HTML5 browsers won’t throw an error anymore. This will just render. In a very specific way, defined by the spec. No interpretation differences between browsers. This will work in a very specific way. Quiz time. What style will the 3 be? Will the browser ignore the closing B tag, or close the I tag? Or what? Who here knows…?

# data[].attributes.slidedeck.data[].slides[].desc.html
So lets take a look.The first part is pretty straightforward.

# data[].attributes.slidedeck.data[].slides[].desc.html
We get a B and I element with the correct text… But what happens now?

# data[].attributes.slidedeck.data[].slides[].desc.html
But now we get the closing B tag token. This is not what the tree builder expected. So it tries to figure out which tag it needs to close.

# data[].attributes.slidedeck.data[].slides[].desc.html
And that is this one. So it closes this one and goes back to the body element.

# data[].attributes.slidedeck.data[].slides[].desc.html
Then it sees another text token. But it does something special. It maintains a list of active formatting elements. The B is an formatting element, but we just closed it. So that list contains just one. The I element. That one is still active. We never closed it.

# data[].attributes.slidedeck.data[].slides[].desc.html
So that text token, not only creates a text node. It also creates a new element for that still active formatting element. So the 3 is italic.

# data[].attributes.slidedeck.data[].slides[].desc.html
And that brings us back to the first website created by Tim Web developer.

# data[].attributes.slidedeck.data[].slides[].desc.html
If you validate that very first website. You’ll get some errors. One reason is because the validator only goes back to version 2 and that very first website is not even HTML 1. But there is also something else. There is an actual error in that very first document.

# data[].attributes.slidedeck.data[].slides[].desc.html
And that just confirms to me that allowing errors is something that is build into the dna of HTML. Did you see it? Here! There is an closing A tag without a opening A tag. Just a simple mistake, but the browser won’t care.

# data[].attributes.slidedeck.data[].slides[].desc.html
And again, this is what makes HTML special. This is what makes HTML for everyone. You don’t need a computer science degree to make a website. You are allowed to make mistakes.

# data[].attributes.slidedeck.data[].slides[].desc.html
Everybody can make a website. Some will be bad. Some will be good. It can be about Star Trek fanfic, or React. Or your cats. It can be about any other random unimportant subject. But it will be a website. And the browser will render it.

# data[].attributes.slidedeck.data[].slides[].desc.html
I think that is beautiful. That is what the web is about. Not just about consuming content, but also making content. It is about sharing knowledge. It’s about making connections. It’s about making the world smaller and working together.

# data[].attributes.slidedeck.data[].slides[].desc.html
I get a little bit sad that nowadays almost nobody has a personal website anymore. If we want to share knowledge we all use commercial platforms like WordPress, Facebook or … Cough… Gurgle… X. And that is understandable - web development has become too difficult. For you and me this isn’t a problem. We are webmasters. We rule the web. We copy and paste, experiment and have fun with tags and elements.

# data[].attributes.slidedeck.data[].slides[].desc.html
But most people are dependent on platforms owned by madmen with super inflated ego’s and wannabe dictators. No… It’s more than sad.

# data[].attributes.slidedeck.data[].slides[].desc.html
It’s outright dangerous. These are the kinds of people that determine what you see and what is acceptable.

# data[].attributes.slidedeck.data[].slides[].desc.html
It’s outright dangerous. These are the kinds of people that determine what you see and what is acceptable.

# data[].attributes.slidedeck.data[].slides[].desc.html
For me this hit like a brick. Left or right, this should not be normalised and this should not be accepted. These social media platforms amplify the worst in people. And it feels like we’re endlessly doomscrolling instead of sharing and inspiring other people. I wish we could go back to those simpler times.

# data[].attributes.slidedeck.data[].slides[].desc.html
This photo feels a long time ago. This was during the opening ceremony of the Olympic Games in London in 2012. A celebration of the birth of the web, then 25 years ago. A lot has changed in the world. But the web should be for everybody. So, if you don’t have a website. Make one. Experiment. It doesn’t have to be good. Make content and share knowledge.

# data[].attributes.slidedeck.data[].slides[].desc.html
Inspire. Work together. On the web there are no borders. Www does not stand for Wealthy Western Web. The web isn’t owned by a couple of oligarchs. The web is for everyone.

# data[].attributes.slidedeck.data[].slides[].desc.html
Including me, I have a website. Compared to some of other personal website I see it is pretty rubbish. But it is my website. And it works in all browsers. And I share knowledge on it.

# data[].attributes.slidedeck.data[].slides[].desc.html
So finally lets get back to the noscript tag. With what we’ve learned we can now answer the questions from the beginning.

# data[].attributes.slidedeck.data[].slides[].desc.html
What happens when you nest two noscript tags? Well lets just see.

# data[].attributes.slidedeck.data[].slides[].desc.html
The second noscript tag isn’t a tag at all. Everything will be text until the browser encounters a closing noscript tag. Everything, including the second opening noscript tag. And it will simply ignore the second now lonely closing noscript tag.

# data[].attributes.slidedeck.data[].slides[].desc.html
What happens when you try to style a noscript tag? For example set visibility to visible. Set the display to block. Opacity? Bang !important?

# data[].attributes.slidedeck.data[].slides[].desc.html
Well, that simply does not work. The contents of the noscript tag is not hidden by CSS which you can override. It is ignored in the renderer. The noscript tag will never be rendered. It is the only element that is defined do absolutely nothing.

# data[].attributes.slidedeck.data[].slides[].desc.html
And a script tag inside of a noscript tag. That is obviously not going to work. Why would you want to do that?

# data[].attributes.slidedeck.data[].slides[].desc.html
Except…. Yeah….

# data[].attributes.slidedeck.data[].slides[].desc.html
And with that, I’ve officially used JavaScript in my talk.

# data[].attributes.slidedeck.data[].slides[].desc.html
Thank you!