Emmet: ID and Class
Lesson 03 briefly introduced
.classand#id. This lesson systematically covers these two "CSS selector-style" shorthand operators—they allow Emmet abbreviations to express both structure and attributes at once. The most critical rule: omitting a tag name in HTML context defaults todiv, and Emmet will never "guess" a different element based on a class name (even if the class isbtn, it won't become a<button>).
1. What You Will Learn
.classshorthand: CSS selector-style expression for theclass="class"attribute#idshorthand: CSS selector-style expression for theid="id"attribute- Mixing id and class:
#header.navsets both attributes at once - Chaining multiple classes:
.btn.btn-primary.btn-lgsets 3 classes in one go - Combining with nesting:
div#main.container>section.content>p.textdistributes attributes across the hierarchy
2. A Frontend Developer's Real Story
(1) The Pain: Tags and Attributes Always Needed Two Separate Passes
Alice was upgrading the styling of a login form. She needed to add class="btn btn-primary btn-lg" to every <button> and id="loginForm" with class="form-horizontal" to the form itself. Manually, every tag required writing the full structure:
<button class="btn btn-primary btn-lg">Login</button>
<input id="username" class="form-control" type="text" name="user">
Every tag repeated the tag name plus attributes, and the order of tags and attributes relied on memory. By her count: an 8-line login form required hand-writing 8 tags + 24 attribute names = 32 repeated text segments. Just typing the quotes and equals signs for attributes was making her fingers ache.
Even worse: she recalled a previous project where she mistakenly assumed .btn would automatically trigger a <button> tag—a misunderstanding that sent her down a troubleshooting rabbit hole. Emmet's implicit tag rule absolutely does not switch tag names based on class names.
(2) How .class and #id Shorthand Solves It
Emmet uses exactly the same syntax as CSS selectors to express attributes—.btn directly adds to the current tag's class attribute, and #loginForm adds to the id attribute. This syntax lets you write attributes at the same time you write the tag structure, without switching between "tag mode" and "attribute mode."
Alice rewrote the entire login form with a single line of Emmet:
form#loginForm.form-horizontal>(input#username.form-control[type=text name=user])+(input#pwd.form-control[type=password name=pwd])+(button.btn.btn-primary.btn-lg{Login})
#loginForm.form-horizontal sets both id and class simultaneously. .btn.btn-primary.btn-lg chains three classes with . in one go. 8 tags + 24 attributes, handled in 1 line. From 32 repeated text segments to 1 line, from 5 minutes to 12 seconds.
(3) Benefits
.class and #id are the key operators that merge "structure" and "attributes" into one in Emmet. After this lesson, Alice can write attributes for 80% of her tags simultaneously; the multi-class syntax is identical to CSS, with nearly zero learning curve; and remembering the "omit tag name → div" implicit rule (even if the class is btn, it stays a div) prevents her abbreviations from being misled by class names.
3. .class: CSS Selector-style Class Attribute
.classname in Emmet is equivalent to adding class="classname" to the current tag. When no tag is specified (Lesson 03 implicit rule), it defaults to div.
| Abbreviation | Expansion | Notes |
|---|---|---|
.box |
<div class="box"></div> |
Implicit div |
p.text |
<p class="text"></p> |
Explicit p |
section.hero |
<section class="hero"></section> |
Explicit section |
a.link |
<a href="" class="link"></a> |
Explicit a, a auto-adds href |
Tip: Multiple classes are chained with
.with no spaces between class names—.btn.btn-primarymeans two classes:btnandbtn-primary. Spaces in Emmet are stop characters; they are parsed as multiple independent abbreviations.
4. #id: CSS Selector-style id Attribute
#idname in Emmet is equivalent to adding id="idname" to the current tag. Usage is completely symmetric with .class and corresponds to the CSS ID selector (#id).
| Abbreviation | Expansion |
|---|---|
#main |
<div id="main"></div> |
header#top |
<header id="top"></header> |
form#login |
<form id="login"></form> |
div#app |
<div id="app"></div> |
(1) id and class Can Be Mixed
#id and .class on the same tag have no ordering requirement and can be freely combined:
| Abbreviation | Expansion |
|---|---|
#main.container |
<div id="main" class="container"></div> |
.container#main |
<div class="container" id="main"></div> |
header#top.nav |
<header id="top" class="nav"></header> |
nav.main.dark |
<nav class="main dark"></nav> |
Key Point: Emmet's
#idand.classcome from CSS selector syntax, but the semantics are reversed—CSS uses#and.to select existing elements, while Emmet uses#and.to generate elements with those attributes. Same syntax, opposite purpose.
5. Practical Examples
The following 5 examples cover all core scenarios for .class and #id—from basic single class, to multiple classes, to attribute chains in nested structures.
▶ Example: Single Class .container (Difficulty ⭐)
The most basic class shorthand: omit the tag name and it defaults to div.
Abbreviation:
.container
Expansion:
<div class="container"></div>
Output:
TEXT 📖 Display onlyImplicit div tag + single class. This shorthand is extremely common in utility-class frameworks like Bootstrap and Tailwind. .container in Bootstrap is a fixed-width container; in Tailwind it is a max-width utility. Note: when you write .container inside a ul parent, .container still implicitly becomes a div—unless your abbreviation position genuinely requires a li child of ul, .container will never auto-morph into a semantic element like li (Lesson 06 implicit rules).
▶ Example: Single ID #main (Difficulty ⭐)
The most basic id shorthand.
Abbreviation:
#main
Expansion:
<div id="main"></div>
Output:
TEXT 📖 Display onlyAn empty div with id="main". In HTML, ids must be globally unique, so this shorthand is often used for "page main container" or "route mount point." Note: Emmet does not validate id uniqueness—multiple #main abbreviations all expand to id="main"; whether that causes a conflict is up to you to check manually.
▶ Example: Multi-Class Chain .btn.btn-primary.btn-lg (Difficulty ⭐⭐)
Chaining multiple classes with .—note: the default is still <div>; it will not become <button> just because a class name contains "btn."
Abbreviation:
.btn.btn-primary.btn-lg
Expansion:
<div class="btn btn-primary btn-lg"></div>
Output:
TEXT 📖 Display onlyThis is the abbreviation beginners misunderstand most often: because the class name is `btn`, many assume Emmet will recognize `.btn` as a `<button>`. But Emmet's implicit rules do not depend on class/id values—they only depend on parent element context (see Lesson 06). When there is no parent context and no explicit tag, Emmet always defaults to `div`. To get `<button class="btn btn-primary btn-lg">`, you must explicitly write `button.btn.btn-primary.btn-lg` or `button.btn.btn-primary.btn-lg{Login}` (with text content, Lesson 09). Verify this yourself: type `.btn` in your editor and press Tab to see whether it expands to `div` or `button`.
▶ Example: id + class Combination #header.nav (Difficulty ⭐)
Mixing #id and .class—order does not affect the expansion result.
Abbreviation:
#header.nav
Expansion:
<div id="header" class="nav"></div>
Output:
TEXT 📖 Display onlySets both id="header" and class="nav" at once. This is the classic shorthand for a page header navigation. The order of id and class can be swapped (.nav#header produces the same result), but writing id first aligns with the semantic habit of "establish unique identity, then describe type." This shorthand is important in multi-page applications—the #header id remains the same across pages, so CSS can target it uniformly.
▶ Example: Nested Attribute Chain div#main.container>section.content>p.text (Difficulty ⭐⭐)
Distributing id and class across every level of nesting—3 levels of tags, each with its own attributes.
Abbreviation:
div#main.container>section.content>p.text
Expansion:
<div id="main" class="container">
<section class="content">
<p class="text"></p>
</section>
</div>
Output:
TEXT 📖 Display onlyBreakdown: div carries id="main" and class="container", drills down one level to section (with class="content"), then drills down to p (with class="text"). Every level sets its own attributes via #id or .class. This is the typical Emmet usage for "component-based structure"—each level represents a semantically clear container or component. If you only want to set attributes on one level (leaving others empty), simply omit #/. from those levels—e.g., div>section>p is just 3 empty nesting levels.
6. Complete Example: Login Form + Attribute Chain
Combine .class and #id with the > and + operators from earlier lessons to build a complete login form in one line.
▶ Example: Complete Login Form (Difficulty ⭐⭐)
Abbreviation:
form#loginForm.form-horizontal>(input#user.form-control[type=text name=user placeholder="Username"])+(input#pwd.form-control[type=password name=pwd placeholder="Password"])+(button.btn.btn-primary.btn-lg{Login})
Expansion:
<form id="loginForm" class="form-horizontal">
<input id="user" class="form-control" type="text" name="user" placeholder="Username">
<input id="pwd" class="form-control" type="password" name="pwd" placeholder="Password">
<button class="btn btn-primary btn-lg">Login</button>
</form>
Output:
TEXT 📖 Display onlyBreakdown: form gets both id and class. Inside, + adds two input siblings, then + adds a button. Both inputs and buttons get styling classes via .class, and inputs additionally use #id (optional) and [type=name=placeholder=] (covered in the next lesson) for attributes. Note that the button level explicitly writes `<button>`, so .btn.btn-primary.btn-lg becomes a class on a button rather than an implicit div. This is the standard pattern of "using explicit tags to avoid misjudgment."
❓ FAQ
.btn.btn-primary expand to <button> or <div>? Why?<div>. Emmet's implicit tag rules depend only on parent element context, not on the literal value of class/id names. Unless you explicitly write button.btn.btn-primary, the default when there is no explicit tag is always div. This is the most common pitfall for beginners—many assume .btn should map to button, and then think the div expansion is a bug.#my-id need to be escaped?#my-id and .my-class are valid CSS selector characters; Emmet treats them as part of the id/class name. However, an initial hyphen (e.g., #-main) is not valid CSS syntax and Emmet may throw an error or parse unexpectedly.# and . be mixed on the same tag?#a.b.c.d.e.f.g is fully valid and expands to <div id="a" class="b c d e f g"></div>. In practice, ids should be unique and limited in number, and too many classes make style management harder. Recommendation: 1 id + 2–4 classes is a common "reasonable upper limit."#header and .header after expansion?.header → <div class="header"></div> (class is for reusable "type"); #header → <div id="header"></div> (id is for "unique identity"). At the CSS selector level: .header can match multiple elements, #header can only match one. At the HTML semantic level: class is typically used for "component type" (button, card, form), while id is typically used for "page anchor or JS hook."📖 Summary
.classnameaddsclass="classname"to the current tag#idnameaddsid="idname"to the current tag- When a tag is omitted, the default is
div(not influenced by class name association) - Multiple classes are chained with
.with no spaces #idand.classcan be mixed; order does not affect the expansion- When combined with nesting: each level can independently set its own id/class attributes
- To force a button tag, you must explicitly write
button.btn.btn-primary.btn-lg
📝 Exercises
- Basic (Difficulty ⭐): Write 5 Emmet abbreviations that expand to: (a)
.alert; (b)#app; (c).btn.btn-danger; (d)#header.nav.dark; (e)a.btn. - Intermediate (Difficulty ⭐⭐): Analyze the expansion structure of
header#top.navbar>nav.main>ul.nav-list>li.nav-item*3>a.nav-link(name each tag and attribute at every level), and verify that Emmet's expansion matches your analysis. - Challenge (Difficulty ⭐⭐⭐): Write a complete Bootstrap-style "card grid": outer
div.container, containingdiv.row, which contains 3.cardelements. Each card includesimg.card-img-top,div.card-body,h5.card-title,p.card-text, anda.btn.btn-primary. Write the entire structure in one line of Emmet, and explain why.btn.btn-primaryhere automatically becomes<a>instead of an implicit div (hint: is there an explicit tag in the abbreviation?).