Say you have a z-index bug. Something is being covered up by something else. In my experience, a typical solution is to put position: relative on the thing so z-index works in the first place, and maybe rejigger the z-index value until the right thing is on top.
The danger here is that this sets off a little z-index war. Raising a z-index value over here fixes one bug, and then causes another by covering up some other element somewhere else when you didn’t want to. Hopefully, you can reason about the situation and fix the bugs, even if it means refactoring more z-index values than you thought you might need to.
If the “stack” of z-index values is complicated enough, you might consider an abstraction. One of the problems is that your z-index values are probably sprinkled rather randomly throughout your CSS. So, instead of simply letting that be, you can give yourself a central location for z-index values to reference later.
The idea here is that most values are auto-generated by the tool (the null values), but you can specify them if you want. That way, if you have a third-party component with a z-index value you can’t change, you plug that into the map, and then the auto-generated numbers will factor that in when you make layers on top. It also means it’s very easy to slip layers in between others.
I think all that is clever and useful stuff — but I also think it doesn’t help with another common z-index bug: stacking contexts. It’s always the stacking context, as I’ve written. If some element is in a stacking context that is under some other stacking context, there is no z-index value possible that will bring it on top. That’s a much harder bug to fix.
One of the great frustrations of front-end development is the unexpected interaction and overlapping of those same elements. Struggling to arrange elements along the z-axis, which extends perpendicularly through the computer screen towards and away from the viewer, is such a shared front-end experience that an element’s z-index can sometimes be used as a frustrate-o-meter gauging the developer’s mood.
The key to maintainable z-index values is understanding that z-index values can’t always be directly compared. They’re not an absolute measurement along an imaginary ruler extending out of the viewport; rather, they are a relative order between elements within the same stacking context.
Turns out there is a nice little debugging tool for stacking contexts in the form of a browser extension (Chrome and Firefox.) Andy gets into a very tricky situation where an RTL version of a website has a rule that uses a transform to move a video on the page. That transform triggers a new stacking context and hence a bug with an unclickable button. Gnarly.
Kinda makes me think that there could be some kinda built-in DevTools way of seeing/understanding stacking contexts. I don’t know if this is the right answer, but I’ve been seeing a leaning into “badges” in DevTools more and more. These things:
Maybe there could be a badge for stacking contexts? Typically what happens with badges is that you click it on and it shows a special UI. For example, for flexbox or grid it will show the overlay for all the grid lines. The special UI for stacking contexts could be color/texture coded and labelled areas showing all the stacking contexts and how they are layered.
Big thoughts on where the industry is headed from Shawn Wang:
Advancements in two fields — programming languages and cloud infrastructure — will converge in a single paradigm: where all resources required by a program will be automatically provisioned, and optimized, by the environment that runs it.
I can’t articulate it like Shawn, but this all feels right.
I think of how front-end development has exploded over time with JavaScript being everywhere (see: “ooops, I guess we’re full-stack developers now”). Services have also exploded to help. Oh hiiii front-end developers! I see you can write a little JavaScript! Come over here and we’ll give you a complete database with GraphQL endpoints! And we’ll run your cloud functions! But that means there are a lot more people doing this who, in some sense, have no business doing it (points at self). I just have to trust that these services are going to protect me from myself as best they can.
Follow this trend line, and it will get easier and easier to run everything you need to run. Maybe I’ll write code like:
/*
- Be a cloud function
- Run at the edge
- Get data from my data store I named "locations", require JWT auth
- Return no slower than 250ms
- I'm willing to pay $8/month for this, alert me if we're on target to exceed that
*/
exports.hello = (message) => {
const name = message.data
const location = locations.get("location").where(message.id);
return `Hello, ${name} from ${location}`;
};
That’s just some fake code, but you can see what I mean. Right by your code, you explain what infrastructure you need to have it work and it just does it. I saw a demo of cloudcompiler.run the other day and it was essentially like this. Even the conventions Netlify offers point highly in this direction, e.g. put your .js files in a functions folder, and we’ll take care of the rest. You just hit it with a local relative URL.
I’d actually bet the future is even more magical than this, guessing what you need and making it happen.
Let’s say we want to add something to a webpage after the initial load. JavaScript gives us a variety of tools. Perhaps you’ve used some of them, like append, appendChild, insertAdjacentHTML, or innerHTML.
The difficult thing about appending and inserting things with JavaScript isn’t so much about the tools it offers, but which one to use, when to use them, and understanding how each one works.
Let’s try to clear things up.
Super quick context
It might be helpful to discuss a little background before jumping in. At the simplest level, a website is an HTML file downloaded from a server to a browser.
Your browser converts the HTML tags inside your HTML file into a bunch of objects that can be manipulated with JavaScript. These objects construct a Document Object Model (DOM) tree. This tree is a series of objects that are structured as parent-child relationships.
In DOM parlance, these objects are called nodes, or more specifically, HTML elements.
<!-- I'm the parent element -->
<div>
<!-- I'm a child element -->
<span>Hello</span>
</div>
In this example, the HTML span element is the child of the div element, which is the parent.
And I know that some of these terms are weird and possibly confusing. We say “node”, but other times we may say “element” or “object” instead. And, in some cases, they refer to the same thing, just depending on how specific we want to be .
For example, an “element” is a specific type of “node”, just like an apple is a specific type of fruit.
Understanding these DOM items is important, as we’ll interact with them to add and append things with JavaScript after an initial page load. In fact, let’s start working on that.
Setup
These append and insert methods mostly follow this pattern:
Element.append_method_choice(stuff_to_append)
Again, an element is merely an object in the DOM Tree that represents some HTML. Earlier, we had mentioned that the purpose of the DOM tree is to give us a convenient way to interact with HTML using JavaScript.
So, how do we use JavaScript to grab an HTML element?
Querying the DOM
Let’s say we have the following tiny bit of HTML:
<div id="example" class="group">
Hello World
</div>
There are a few common ways to query the DOM:
// Query a specific selector (could be class, ID, element type, or attribute):
const my_element1 = document.querySelector('#example')
// Query an element by its ID:
const my_element2 = document.getElementbyId('example')
// Query an element by its class:
const my_element3 = document.getElementbyClass('group')[0]
In this example, all three lines query the same thing, but look for it in different ways. One looks at any of the item’s CSS selectors; one looks at the item’s ID; and one looks at the item’s class.
Note that the getElementbyClass method returns an array. That’s because it’s capable of matching multiple elements in the DOM and storing those matches in an array makes sure all of them are accounted for.
In this example, something is a parameter that represents stuff we want to tack on to the end of (i.e. append to) the matched element.
We can’t just append any old thing to any old object. The append method only allows us to append either a node or plain text to an element in the DOM. But some other methods can append HTML to DOM elements as well.
Nodes are either created with document.createElement() in JavaScript, or they are selected with one of the query methods we looked at in the last section.
Plain text is, well, text. It’s plain text in that it does not carry any HTML tags or formatting with it. (e.g. Hello).
HTML is also text but, unlike plain text, it does indeed get parsed as markup when it’s added to the DOM (e.g. <div>Hello</div>).
It might help to map out exactly which parameters are supported by which methods:
1 This works, but insertAdjacentText is recommended. 2 Instead of taking traditional parameters, innerHTML is used like: element.innerHTML = 'HTML String'
How to choose which method to use
Well, it really depends on what you’re looking to append, not to mention certain browser quirks to work around.
If you have existing HTML that gets sent to your JavaScript, it’s probably easiest to work with methods that support HTML.
If you’re building some new HTML in JavasScript, creating a node with heavy markup can be cumbersome, whereas HTML is less verbose.
If you want to attach event listeners right away, you’ll want to work with nodes because we call addEventListener on nodes, not HTML.
If all you need is text, any method supporting plain text parameters is fine.
If your HTML is potentially untrustworthy (i.e. it comes from user input, say a comment on a blog post), then you’ll want to be careful when using HTML, unless it has been sanitized (i.e. the harmful code has been removed).
Our final append places the new user at the end of the buddy list, just before the closing </ul> tag. If we’d prefer to place the user at the front of the list, we could use the prepend method instead.
You may have noticed that we were also able to use append to fill our <a> tag with text like this:
const buddy_name = "Dale"
new_link.append(buddy_name) // Text param
appendChild is another JavaScript method we have for appending stuff to DOM elements. It’s a little limited in that it only works with node objects, so we we’ll need some help from textContent (or innerText) for our plain text needs.
There’s no need to follow all of above JavaScript – the point is that creating large amounts of HTML in JavaScript can become quite cumbersome. And there’s no getting around this if we use append or appendChild.
In this heavy markup scenario, it might be nice to just write our HTML as a string, rather than using a bunch of JavaScript methods…
insertAdjacentHTML is is like append in that it’s also capable of adding stuff to DOM elements. One difference, though, is that insertAdjacentHTML inserts that stuff at a specific position relative to the matched element.
And it just so happens to work with HTML. That means we can insert actual HTML to a DOM element, and pinpoint exactly where we want it with four different positions:
Remember the security concerns we mentioned earlier. We never want to insert HTML that’s been submitted by an end user, as we’d open ourselves up to cross-site scripting vulnerabilities.
That’s what we want! But there’s a constraint with using innerHTML that prevents us from using event listeners on any elements inside of #buddies because of the nature of += in list.innerHTML += new_buddy.
You see, A += B behaves the same as A = A + B. In this case, A is our existing HTML and B is what we’re inserting to it. The problem is that this results in a copy of the existing HTML with the additional inserted HTML. And event listeners are unable to listen to copies. That means if we want to listen for a click event on any of the <a> tags in the buddy list, we’re going to lose that ability with innerHTML.
So, just a word of caution there.
Demo
Here’s a demo that pulls together all of the methods we’ve covered. Clicking the button of each method inserts “Dale” as an item in the buddies list.
Go ahead and open up DevTools while you’re at it and see how the new list item is added to the DOM.
Recap
Here’s a general overview of where we stand when we’re appending and inserting stuff into the DOM. Consider it a cheatsheet for when you need help figuring out which method to use.
1 This works, but insertAdjacentText is recommended. 2 Instead of taking traditional parameters, innerHTML is used like: element.innerHTML = 'HTML String'
If I had to condense all of that into a few recommendations:
Using innerHTML for appending is not recommended as it removes event listeners.
append works well if you like the flexibility of working with node elements or plain text, and don’t need to support Internet Explorer.
appendChild works well if you like (or need) to work with node elements, and want full browser coverage.
insertAdjacentHTML is nice if you need to generate HTML, and want to more specific control over where it is placed in the DOM.
Last thought and a quick plug :)
This post was inspired by real issues I recently ran into when building a chat application. As you’d imagine, a chat application relies on a lot of appending/inserting — people coming online, new messages, notifications, etc.
That chat application is called Bounce. It’s a peer-to-peer learning chat. Assuming you’re a JavaScript developer (among other things), you probably have something to teach! And you can earn some extra cash.
Frontend Masters has been our learning partner for a couple of years now. I love it. If you need structured learning to up your web development skills, Frontend Masters is the place. It works so well because we don’t offer that kind of structured learning ourselves — I’d rather recommend a first-rate learning joint. CSS-Tricks is more of a place you subscribe to for our special blend of industry news, web development advice, and reference material. Frontend Masters has these learning paths that they highly invest in to take you through a guided learning path for particular technologies and learning levels.
I spoke with Frontend Masters founder and CEO Marc and he says:
I couldn’t be more proud of how the learning paths have shaped up. Everyone else in this industry is so focused on the latest and greatest new shiny thing, when instead our platform focuses on deeply learning the fundamentals. And that has really paid off with so many amazing, nearly evergreen courses – rather than having to play the quantity game creating a million courses we can keep focused on refreshing the core curriculum.
I’m kind of a fundamentals guy myself, so that really resonates with me.
I also asked him what specifically is new and he sent me nearly 20 new and updated courses. He also said this specifically:
We just updated all 15 core learning paths so those are all 🔥 absolute fire. The thing that I’m most excited about is our new ones… specifically the typescript and functional JavaScript paths. Also our fullstack path updated with new courses is epic.
He also hinted at some stuff I’m excited about coming up this fall and winter, but I’ll let you be surprised by them once they come.