Earlier this year, I shared the HTML boilerplate I like to use when starting new web projects with line-by-line explanations on my blog. It’s a collection of mostly <head>
tags and attributes I usually use on every website I build. Until recently, I would just copy and paste the boilerplate whenever I needed it, but I’ve decided to improve my workflow by adding it as a snippet to VS Code — the editor of my choice.
Snippets And Abbreviations In Visual Studio Code
VS Code comes built-in with custom user snippets and HTML and CSS snippets and abbreviations provided by Emmet.
For example, if you type p>a{Sign Up}
in an HTML document and press Enter or Tab, Emmet will turn it into the following markup:
<p><a href="">Sign Up</a></p>
Note: Visit the Emmet docs to learn how to use the abbreviation syntax.
If we need this specific abbreviation regularly, we can save it as a snippet to improve our workflow even more.
{ "html": { "snippets": { "signup": "p>a{Sign Up}" } }
}
Now we can type signup
and press Enter or Tab and we’ll get the same result. I’ll explain how to create snippets in the next section.
Emmet comes with a bunch of HTML snippets by default. For example, !
creates the basic structure of an HTML document.
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title>
</head>
<body> </body>
</html>
That’s great, but if we want to adapt this snippet by removing or adding elements and attributes, we have to overwrite it and create our own snippet.
Creating And Overwriting Snippets
If we want to create our own Emmet snippets or overwrite existing ones in VS Code, the following steps are necessary:
- Create a snippets.json file, add this basic JSON structure and save it somewhere on your hard disk.
{ "html": { "snippets": { } }, "css": { "snippets": { } } }
- Open the VS Code settings (Code → Preferences → Settings) and search for “Emmet Extensions Path”.
- Click “Add Item”, enter the path to the folder where you’ve saved the snippets.json file you’ve created earlier and press “OK”.
That’s it. Now we’re ready to create snippets by adding properties to the html
and css
objects where the key
is the name of the snippet and the value
an abbreviation or a string.
Some Of My Custom HTML Snippets
Before we dive deep into snippet creation and I show you how I created a snippet for my HTML boilerplate, let’s warm up first with some small, but useful snippets I’ve created, as well.
Lazy Loading
Out of the box, there’s an img
abbreviation, but there’s none for lazily loaded images. We can use the default abbreviation and just add the additional attributes and attribute values we need in square brackets.
{ "html": { "snippets": { "img:l": "img[width height loading='lazy']" } }
}
img:l
+ Enter/Tab now creates the following markup:
<img src="" alt="" width="" height="" loading="lazy">
Page
Most pages I create consist of <header>
, <main>
and <footer>
landmarks and an <h1>
. The custom page
abbreviation lets me create that structure quickly.
"snippets": { "page": "header>h1^main+footer{${0:©}}"
}
page
+ Enter/Tab creates the following markup:
<header> <h1></h1>
</header>
<main></main>
<footer>©</footer>
That abbreviation is quite long, so let’s break it down into smaller bits.
Breakdown
Create an <header>
element and a child <h1>
.
header>h1
Move up, back to the level of the <header>
, and create a <footer>
that follows <main>
.
^main+footer
Set the final tab stop within the <footer>
and set the default text to ©
.
{${0:©}}
Navigation
The abbreviation nav
just creates a <nav>
start and end tag by default, but what I usually need is a <nav>
with a nested <ul>
, <li>
elements and links ( <a>
). If there are multiple <nav>
elements on a page, they should also be labeled, for example by using aria-label
.
"nav": "nav[aria-label='${1:Main}']>ul>(li>a[aria-current='page']{${2:Current Page}})+(li*3>a{${0:Another Page}})"
That looks wild, so let’s break it down again.
Breakdown
We start with a <nav>
element with an aria-label
attribute and a nested <ul>
. ${1:Main}
populates the attribute with the text “Main” and creates a tab stop at the attribute value by moving the cursor to it and highlighting it upon creation.
nav[aria-label='${1:Main}']>ul
Then we create four list items with nested links. The first item is special because it marks the active page using aria-current="page"
. We create another tab stop and populate the link with the text “Current Page”.
(li>a[aria-current='page']>{${2:Current Page}})
Finally, we add three more list items with links and the link text “Another page”.
(li*3>a>{${0:Another Page}})
Before our adaptations, we got this:
-- Before: nav + TAB/Enter --> <nav></nav>
Now we get this:
<-- After: nav + TAB/Enter --> <nav aria-label="Main"> <ul> <li><a href="" aria-current="page">Current Page</a></li> <li><a href="">Another Page</a></li> <li><a href="">Another Page</a></li> <li><a href="">Another Page</a></li> </ul>
</nav>
Style
The default style
abbreviation only creates the <style>
start and end tag, but usually when I use the <style>
element I do it because I quickly want to test or debug something.
Let’s add some default rules to the <style>
tag:
"style": "style>{\\* { box-sizing: border-box; \\}}+{\n${1:*}:focus \\{${2: outline: 2px solid red; }\\} }+{\n${0}}"
Breakdown
Some characters (e.g. $
, *
, {
or }
) have to be escaped using \\
.
style>{\\* { box-sizing: border-box; \\}}
\n
creates a linebreak and ${1:*}
places the first tab stop at the selector *
.
{\n${1:*}:focus \\{${2: outline: 2px solid red; }\\}}
Alright, enough warming-up. Let’s create complex snippets. At first, I wanted to create a single snippet for my boilerplate, but I created three abbreviations that serve different needs.
Boilerplate Small
This is a boilerplate for quick demos, it creates the following:
- Basic site structure,
viewport
meta tag,- Page title,
<style>
element,- A
<h1>
.
{ "!": "{<!DOCTYPE html>}+html[lang=${1}${lang}]>(head>meta:utf+meta:vp+{}+title{${2:New document}}+{}+style)+body>(h1>{${3: New Document}})+{${0}}"
}
Breakdown
A string with the doctype:
{<!DOCTYPE html>}
The <html>
element with a lang
attribute. The value of the lang
attribute is a variable you can change in the VS code settings (Code → Preferences → Settings).
html[lang=${1}${lang}]
You can change the default natural language of the page by searching for “emmet variables” in VS Code settings and changing the lang
variable. You can add your custom variables here, too.
The <head>
includes the charset
meta tag, viewport
meta tag, <title>
, and <style>
tag. {}
creates a new line.
(head>meta:utf+meta:vp+{}+title{${2:New document}}+{}+style)
Let’s have a first quick look at what this gives us.
<!DOCTYPE html>
<html lang="en">
<head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>New document</title>
</head>
</html>
Looks okay, but the meta:utf
abbreviation creates the old way in HTML to define the charset
and meta:vp
creates two tab stops I don’t need because I never use a different setting for the viewport
.
Let’s overwrite these snippets before we move on.
{ "meta:vp": "meta[name=viewport content='width=device-width, initial-scale=1']", "meta:utf": "meta[charset=${charset}]"
}
Last but not least, the <body>
element, an <h1>
with default text, followed by the final tab stop.
body>(h1>{${3: New Document}})+{${0}}
The final boilerplate:
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>New document</title> <style> * { box-sizing: border-box; } *:focus { outline: 2px solid red; } </style>
</head>
<body> <h1> New Document</h1> </body>
</html>
For me, that’s the perfect minimal debugging setup.
Boilerplate Medium
While I use the first boilerplate only for quick demos, the second boilerplate can be used for complex pages. The snippet creates the following:
- Basic site structure,
viewport
meta tag,- Page title,
.no-js
/.js
classes,- External screen and print stylesheets,
description
andtheme-color
meta tag,- Page structure.
{ "!!": "{<!DOCTYPE html>}+html[lang=${1}${lang}].no-js>{<!-- TODO: Check lang attribute --> }+(head>meta:utf+meta:vp+{}+title{${1:🛑 Change me}}+{}+(script[type=\"module\"]>{document.documentElement.classList.replace('no-js', 'js');})+{}+link:css+link:print+{}+meta[name=\"description\"][content=\"${2:🛑 Change me (up to ~155 characters)}\"]+{<!-- TODO: Change page description --> }+meta[name=\"theme-color\"][content=\"${2:#FF00FF}\"])+body>page"
}
Yeaaah, I know, that looks like gibberish. Let’s dissect it.
Breakdown
The doctype
and the root element are like in the first example, but with an additional no-js
class and a comment that reminds me to change the lang
attribute, if necessary.
{<!DOCTYPE html>}+html[lang=${1}${lang}].no-js>{ }
The TODO Highlight extension makes the comment really pop.
The <head>
includes the charset
meta tag, viewport
meta tag, <title>
. {}
creates a new line.
(head>meta:utf+meta:vp+{}+title{${1:🛑 Change me}}+{}
A script with a line of JavaScript. I’m cutting the mustard at the JS module support. If a browser supports JavaScript modules, it means that it’s a browser that supports modern JavaScript (e.g. modules, ES 6 syntax, fetch, and so on). I ship most JS only to these browsers, and I use the js
class in CSS, if the styling of a component is different, when JavaScript is active.
(script[type=\"module\"]>{document.documentElement.classList.replace('no-js', 'js');})+{}
Two <link>
elements; the first links to the main stylesheet and the second to a print stylesheet.
link:css+link:print+{}
The page description:
meta[name=\"description\"\][content=\"${2:🛑 Change me (up to ~155 characters)}\"]+{ }
The theme-color
meta tag:
meta[name=\"theme-color\"\][content=\"${2:#FF00FF}\"])
The body element and the basic page structure:
body>page
The final boilerplate looks like this:
<!DOCTYPE html>
<html lang="en" class="no-js">
<!-- TODO: Check lang attribute --> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>🛑 Change me</title> <script type="module"> document.documentElement.classList.replace('no-js', 'js'); </script> <link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="print.css" media="print"> <meta name="description" content="🛑 Change me (up to ~155 characters)"> <!-- TODO: Change page description --> <meta name="theme-color" content="#FF00FF">
</head>
<body> <header> <h1></h1> </header> <main></main> <footer>©</footer>
</body>
</html>
Full Boilerplate
The full boilerplate is similar to the second boilerplate; the differences are additional meta
tags and a script
tag.
The snippet creates the following:
- Basic site structure,
viewport
meta tag,- Page title,
js
/no-js
classes,- External screen and print stylesheets,
description
and open graph meta tags,theme-color
meta tag,- canonical
<link>
tag, - Favicon tags,
- Page structure,
- <
script>
tag.
{ "!!!": "{<!DOCTYPE html>}+html[lang=${1}${lang}].no-js>{<!-- TODO: Check lang attribute --> }+(head>meta:utf+meta:vp+{}+title{${1:🛑 Change me}}+{}+(script[type=\"module\"]>{document.documentElement.classList.replace('no-js', 'js');})+{}+link:css+link:print+{}+meta[property=\"og:title\"][content=\"${1:🛑 Change me}\"]+meta[name=\"description\"][content=\"${2:🛑 Change me (up to ~155 characters)}\"]+meta[property=\"og:description\"][content=\"${2:🛑 Change me (up to ~155 characters)}\"]+meta[property=\"og:image\"][content=\"${1:https://}\"]+meta[property=\"og:locale\"][content=\"${1:en_GB}\"]+meta[property=\"og:type\"][content=\"${1:website}\"]+meta[name=\"twitter:card\"][content=\"${1:summary_large_image}\"]+meta[property=\"og:url\"][content=\"${1:https://}\"]+{<!-- TODO: Change social media stuff --> }+{}+link[rel=\"canonical\"][href=\"${1:https://}\"]+{<!-- TODO: Change canonical link --> }+{}+link[rel=\"icon\"][href=\"${1:/favicon.ico}\"]+link[rel=\"icon\"][href=\"${1:/favicon.svg}\"][type=\"image/svg+xml\"]+link[rel=\"apple-touch-icon\"][href=\"${1:/apple-touch-icon.png}\"]+link[rel=\"manifest\"][href=\"${1:/my.webmanifest}\"]+{}+meta[name=\"theme-color\"][content=\"${2:#FF00FF}\"])+body>page+{}+script:src[type=\"module\"]"
}
This incredibly long snippet creates this:
<!DOCTYPE html>
<html lang="en" class="no-js">
<!-- TODO: Check lang attribute --> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>🛑 Change me</title> <script type="module"> document.documentElement.classList.replace('no-js', 'js'); </script> <link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="print.css" media="print"> <meta property="og:title" content="🛑 Change me"> <meta name="description" content="🛑 Change me (up to ~155 characters)"> <meta property="og:description" content="🛑 Change me (up to ~155 characters)"> <meta property="og:image" content="https://"> <meta property="og:locale" content="en_GB"> <meta property="og:type" content="website"> <meta name="twitter:card" content="summary_large_image"> <meta property="og:url" content="https://"> <!-- TODO: Change social media stuff --> <link rel="canonical" href="https://"> <!-- TODO: Change canonical link --> <link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.svg" type="image/svg+xml"> <link rel="apple-touch-icon" href="/apple-touch-icon.png"> <link rel="manifest" href="/my.webmanifest"> <meta name="theme-color" content="#FF00FF">
</head>
<body> <header> <h1></h1> </header> <main></main> <footer>©</footer> <script src="" type="module"></script>
</body>
</html>
Custom CSS Snippets
For the sake of completeness, here are some of the CSS snippets I’m using.
Debugging
This snippet creates a 5px red outline with a custom offset.
"debug": "outline: 5px solid red;\noutline-offset: -5px;"
Centering
A snippet that sets display
to flex, and centers its child items.
"center": "display: flex;\njustify-content: center;\nalign-items: center;"
Sticky
Sets the position
property to sticky
, with two tab stops at the top
and left
property.
"sticky": "position: sticky;\ntop: ${1:0};\nleft: ${2:0};"
User Snippets
At the beginning of this article, I mentioned that VS Code also provides custom snippets. The difference to Emmet snippets is that you can’t use abbreviations, but you can also define tab stops and make use of internal variables.
How to get the best out of user snippets could be a topic for another article, but here’s an example of a custom CSS snippet I’ve defined:
"Visually hidden": { "prefix": "vh", "body": [ ".u-vh {", " position: absolute;\n white-space: nowrap;\n width: 1px;\n height: 1px;\n overflow: hidden;\n border: 0;\n padding: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n margin: -1px;", "}"
], "description": "A utility class for screen reader accessible hiding."
}
This snippet doesn’t just create CSS rules, but a whole declaration block when we type vh
and press Enter or Tab.
.u-vh { position: absolute; white-space: nowrap; width: 1px; height: 1px; overflow: hidden; border: 0; padding: 0; clip: rect(0 0 0 0); clip-path: inset(50%); margin: -1px;
}
Final Words
It takes some time to create these snippets, but it’s worth the effort because you can customize Emmet to your personal preferences, automate repetitive tasks and save time in the long run.
I’d love to see which snippets you use, so please share them with us in the comments. If you want to use my settings, you can find my final snippets.json on GitHub.