Creating Custom Emmet Snippets In VS Code

About The Author

Manuel Matuzović is a frontend developer from Vienna who’s passionate about accessibility, progressive enhancement, performance, and web standards. He’s one of … More about Manuel ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

In this article, Manuel explains why Emmet is one of his favorite productivity tools for writing HTML and CSS, and how you can create custom Emmet snippets in Visual Studio Code to help you improve your front-end workflows even more.

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.

Here’s a quick demo of the custom snippets I’ve created.

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:

  1. Create a snippets.json file, add this basic JSON structure and save it somewhere on your hard disk.
    {
      "html": {
        "snippets": {
        }
      },
      "css": {
        "snippets": {
        }
      }
    }
    
  2. Open the VS Code settings (Code → Preferences → Settings) and search for “Emmet Extensions Path”.
  3. 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:©}}

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; }\\}}
  • Before: <style><style>
  • After:
<style>
  * { box-sizing: border-box; }  
  *:focus { outline: 2px solid red; }
</style>

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.

  1. Small
  2. Medium
  3. Full

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.

There are 2 default variables in VS Code, lang is set to en and charset to UTF-8.

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 and theme-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 extension highlights certain keywords like TODO visually.

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};"
A demonstration of all 3 CSS snippets applied to a div element.

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.

Resources

Smashing Editorial (vf, il)