emmet2-mode brings Emmet abbreviations to Emacs as
completion choices for HTML, JSX, CSS, SCSS and CSS-in-JS. Type ul>li*3,
.card.active or m10,p.5, accept a choice, and the abbreviation becomes code.
emmet2-mode 2.0 is a rewrite in Emacs Lisp. It needs no Deno, deno-bridge,
websocket or Emmet npm package, and abbreviations expand when you accept a
completion choice instead of on C-j. To upgrade from 0.2, see
Upgrading from 0.2.
- CSS and SCSS in every mode derived from
css-base-mode, such ascss-mode,scss-mode,css-ts-modeandless-css-mode(Less is treated as CSS). - HTML in
web-mode(a separate package), including<style>blocks,style=""attributes and script parts. - JSX and style objects in
js-mode,js-ts-mode,tsx-ts-modeand web-mode JSX files;typescript-ts-modeoffers style objects only. - In other modes, such as
text-modeormhtml-mode, only the commands below work, and they treat all text as markup, even<style>blocks inmhtml-mode.
emmet2-mode requires Emacs 30.1 or later and runs no external program. With straight.el and use-package:
(use-package emmet2-mode
:straight (emmet2-mode :type git :host github :repo "P233/emmet2-mode"
:files (:defaults "data"))
:hook ((web-mode css-base-mode js-base-mode typescript-ts-base-mode) . emmet2-mode))The data directory must be installed next to the Lisp files. From a plain
checkout:
(add-to-list 'load-path "/path/to/emmet2-mode")
(autoload 'emmet2-mode "emmet2-mode" nil t)
(autoload 'emmet2-complete "emmet2-capf" nil t)
(autoload 'emmet2-expand-at-point "emmet2-capf" nil t)
(dolist (hook '(web-mode-hook css-base-mode-hook
js-base-mode-hook typescript-ts-base-mode-hook))
(add-hook hook #'emmet2-mode))To check the installation, open a .css file, type .a { m10 }, put point
after m10 and run M-x emmet2-expand-at-point; it becomes
.a { margin: 10px; }.
JavaScript, TypeScript and JSX need Emacs built with Tree-sitter and the
matching grammar: javascript for js-mode, js-ts-mode and web-mode
JavaScript, typescript for typescript-ts-mode and web-mode TypeScript, and
tsx for tsx-ts-mode and web-mode JSX and TSX. HTML and CSS need no grammar.
Tested revisions are in test/dependencies.json.
Corfu is the supported completion frontend.
emmet2-mode needs no Corfu setting and changes none of yours.
corfu-popupinfo-mode previews multi-line expansions in full:
(use-package corfu
:straight t
:hook ((web-mode css-base-mode js-base-mode typescript-ts-base-mode) . corfu-mode)
:custom (corfu-auto t)
:config (corfu-popupinfo-mode 1))Corfu's automatic completion waits for three characters by default
(corfu-auto-prefix);
for short abbreviations such as d, ta or @f, lower it or request
completion manually.
Company works through standard
completion-at-point; after Company accepts a function value such as calc(),
point stays after the parentheses, where Corfu places it inside.
With Emacs's default completion UI and styles, completion-at-point first
reports "Complete, but not unique"; repeat it to list the choices in
*Completions*.
In a read-only buffer, choices can be listed, but accepting one is refused.
With yasnippet installed, TAB
visits the remaining HTML and JSX fields; emmet2-mode enables yas-minor-mode
when a snippet needs it. Without yasnippet you get the same text and starting
point.
Type an abbreviation, with point anywhere inside it, and accept a choice. One undo restores the abbreviation.
- In HTML and JSX, type in markup content. Automatic completion offers a bare
word only when it is a known element alone on its line, such as
div. - In CSS and SCSS, type at the start of a declaration inside braces. Pseudo-classes, pseudo-elements and at-rules also work at the stylesheet root.
<style>andstyle=""produce CSS. JSXstyle={{...}}and objects passed toStyleSheet.create(...)orcreateTheme(...)produce camelCase properties and JavaScript values.- Comments and strings are skipped.
CSS offers up to ten choices: ta offers text-align: ;, its keywords, then
alternatives such as top: auto;. For ovh,ta, the menu shows the property
being typed, and accepting also inserts overflow: hidden;.
Markup starts in the first empty field, such as href in a.link. CSS starts
no snippet: d leaves point in display: │;, and TAB keeps your binding. In
CSS and SCSS modes, a complete declaration alone on its line, such as m10,
continues on a new line with the same indentation; set emmet2-css-auto-newline
to nil to stay put.
emmet2-mode binds no keys. M-x emmet2-complete requests choices explicitly,
even without the minor mode, and M-x emmet2-expand-at-point inserts the first
choice at once. To bind them:
(with-eval-after-load 'emmet2-mode
(keymap-set emmet2-mode-map "C-c ." #'emmet2-complete)
(keymap-set emmet2-mode-map "C-j" #'emmet2-expand-at-point))In built-in CSS modes, values after a property's colon match fuzzily, as in
display: if for inline-flex, with a short description that
corfu-echo-mode shows. This replaces css-mode's own value list for known
properties; when no value matches, other completion functions such as Eglot's
still run. To keep css-mode's list:
(add-hook 'emmet2-mode-hook
(lambda ()
(remove-hook 'completion-at-point-functions #'emmet2-css-value-capf t)))Markup follows the Emmet cheat sheet. │
marks where point ends; results with several declarations insert one per line.
| Context | Abbreviation | Result |
|---|---|---|
| HTML | a.link |
<a href="│" class="link"></a> |
| JSX | .card.active |
<div className={clsx(styles.card, styles.active)}>│</div> |
| JSX | _.card.active |
<div className="card active">│</div> |
| JSX | Component.card/ |
<Component className={styles.card} /> |
| CSS | m10,p.5 or m10+p.5 |
margin: 10px; padding: 0.5rem; |
| CSS | tac, dN, bgc |
text-align: center;, display: none;, background-color: │; |
| CSS | p1-2, m10--20 |
padding: 1px 2px;, margin: 10px -20px; |
| CSS | w50p, p1r, lh1.5 |
width: 50%;, padding: 1rem;, line-height: 1.5; |
| CSS | m--gutter! |
margin: var(--gutter) !important; |
| CSS | ff[Inter], w[calc(100% - 2rem)] |
font-family: Inter;, width: calc(100% - 2rem); |
| CSS | posa1000 |
position: absolute; z-index: 1000; |
| SCSS | m$gutter |
margin: $gutter; |
| Style object | m10+p.5 |
margin: 10, padding: "0.5rem" |
| Selector | &::be, :n(:fc) |
&::before, :not(:first-child) |
| At-rule | @md; in SCSS, @us |
@media │; @use "│"; |
- In JSX, classes use the CSS Modules object
stylesand join withclsx; add the imports yourself. Hyphenated names use brackets, as instyles["btn-primary"]. A leading_keeps the classes a plain string. - In CSS, properties are found by initials and word fragments; an uppercase
letter starts the value explicitly, as in
mA. Integers getpxand decimalsrem; zero and numeric properties such asline-heightandorderstay unitless. Suffixr,eorpforrem,emor%, and!for!important. Unknown names, values and units offer no choice; put project values in brackets. A colon starts a pseudo-class, so writedn, notd:n. - In style objects, unitless and pixel values become numbers, others strings.
A trailing comma ends the member, so
m10,offers nothing; join with+.
| Option | Default | Effect |
|---|---|---|
emmet2-jsx-class-style |
css-modules |
css-modules references, or plain class strings |
emmet2-css-modules-object |
"styles" |
CSS Modules object reference, such as cardStyles |
emmet2-class-names-constructor |
"clsx" |
Function joining several classes, such as cx |
emmet2-markup-variant |
nil |
"solid" writes Solid JSX, with class, in every markup context, HTML included |
emmet2-css-in-js-attributes |
("style") |
JSX attributes holding style objects, such as "sx" |
emmet2-css-in-js-functions |
("StyleSheet.create" "createTheme") |
Functions whose object arguments hold styles, such as "css" |
emmet2-css-scale-functions |
nil |
SCSS functions for (N) values; see below |
emmet2-css-auto-newline |
t |
In CSS and SCSS modes, continue on a new line after a standalone declaration |
All options are safe as directory-local variables. For example, in
.dir-locals.el:
((tsx-ts-mode . ((emmet2-markup-variant . "solid")
(emmet2-css-modules-object . "cardStyles")
(emmet2-class-names-constructor . "cx"))))Then .card.active gives
<div class={cx(cardStyles.card, cardStyles.active)}>│</div>.
With emmet2-css-scale-functions set to
'(("font-size" . "ms") (t . "rhythm")), SCSS fz(1) gives
font-size: ms(1); and p(0)(2) gives padding: 0 rhythm(2);. Your
stylesheet build must provide the functions, for example from
rhythm-sass.
- If no choice appears, check where abbreviations work and Corfu's
prefix threshold, then try
M-x emmet2-complete, which reports errors. For another style object, add its attribute or function toemmet2-css-in-js-attributesoremmet2-css-in-js-functions. M-x emmet2-completereports a missing grammar, as inMissing tree-sitter grammar: javascript. Install it withM-x treesit-install-language-grammar.
scss2-mode provides Tree-sitter major
modes for CSS and SCSS and requires Emacs 31.1 or later. It uses emmet2-mode for
declaration abbreviations, so emmet2-mode need not be enabled in its buffers,
and completes values, pseudo-selectors, at-rules and Sass variables, functions,
mixins and module members itself. Other major modes can supply their own context
the same way; see API.md.
- Emacs 30.1 or later is required.
- Remove the Deno,
deno-bridgeandwebsocketconfiguration, including:after deno-bridgein the emmet2-mode declaration, and restart Emacs. - Replace
:files (:defaults "*.ts" "src" "data")with:files (:defaults "data"). emmet2-expandand itsC-jbinding are gone. Bindemmet2-expand-at-pointoremmet2-completeyourself.- The default CSS Modules object is now
styles; setemmet2-css-modules-objectto"css"to keep 0.2 output. - Syntax follows the major mode:
css-modeis plain CSS, so Sass at-rules such as@useneedscss-modeor<style lang="scss">. - Scale and rhythm values such as
p(1)are opt-in and SCSS-only; setemmet2-css-scale-functionsto'(("font-size" . "ms") (t . "rhythm"))to keep 0.2 output. - Pseudo-selectors expand without
&and rule braces::fugives:focus. - Emmet's preset values are gone and some short forms changed meaning, such as
fs(nowfont-size) andlg(nowlist-style: georgian;); usebgilgfor a linear gradient. Unknown names, values and units no longer expand literally.
See CHANGELOG.md for the full list of changes.
GPL-3.0-or-later. Third-party credits and licenses are in NOTICE.