web-mode.el is an autonomous emacs major-mode for editing web templates.
HTML documents can embed parts (CSS / JavaScript) and blocks (client / server side).

Native Features


html template editor
Screencast on YouTube (prefer HD Quality)


As a result, a block can be located in a part that is embedded in an HTML document.


You can download this mode here.

A GitHub repository is available: http://github.com/fxbois/web-mode/

web-mode.el is also available on melpa and on melpa stable.

web-mode.el is GPL and Free Software.


First drop the file web-mode.el in a directory defined in your load-path.
Then, add in your .emacs (require 'web-mode) (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))

Using web-mode for editing plain HTML files can be done this way (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))

You can also edit plain js, jsx, css, scss, xml files.

Associate an engine

A specific engine can be forced with web-mode-engines-alist.

(setq web-mode-engines-alist '(("php" . "\\.phtml\\'") ("blade" . "\\.blade\\.")) )

Using this association list is required as soon as the file extension is unknown (by web-mode) or is too general (e.g. *.html). In summary, you may have to set both auto-mode-alist and web-mode-engines-alist.

Since the version v10, you can also put a fragment like -*- engine:ENGINE_NAME -*- in a comment at the beginning of your template (web-mode-enable-engine-detection must be set to t in your .emacs)

<?php /* -*- engine:php -*- */ ?> <span><?=$x?></span>

The recognized file extensions are listed in the Engine families paragraph.

Associate a content type

web-mode.el can deal with many content types: html, xml, javascript, jsx, json, css. This was needed to edit *.js.erb files for example: js files that embed ruby blocks.

Sometimes, web-mode.el can not guess the content type with the file extension.
e.g. you want to associate *.api files with web-mode.

The var web-mode-content-types-alist can be used to associate a file path with a content type

(add-to-list 'auto-mode-alist '("\\.api\\'" . web-mode)) (add-to-list 'auto-mode-alist '("/some/react/path/.*\\.js[x]?\\'" . web-mode)) (setq web-mode-content-types-alist '(("json" . "/some/path/.*\\.api\\'") ("xml" . "/other/path/.*\\.api\\'") ("jsx" . "/some/react/path/.*\\.js[x]?\\'")))


The first customisations can be put in a hook like this (defun my-web-mode-hook () "Hooks for Web mode." (setq web-mode-markup-indent-offset 2) ) (add-hook 'web-mode-hook 'my-web-mode-hook)

The customisations below should not be put in the hook. Declare them before loading web-mode.el



  • M-; comment / uncomment line(s)
  • C-c C-f toggle folding on a tag/block
  • C-c C-i indent entire buffer
  • C-c C-m mark and expand
  • C-c C-s insert snippet
  • C-c C-w toggle display of invalid whitespaces


  • C-c C-d a replace apostrophes
  • C-c C-d d show tag mismatch
  • C-c C-d e replace HTML entities
  • C-c C-d n normalize
  • C-c C-d q replace dumb quotes
  • C-c C-d t traverse dom tree
  • C-c C-d x xpath


  • C-c C-b b block beginning
  • C-c C-b c block close
  • C-c C-b e block end
  • C-c C-b k block kill
  • C-c C-b n next block
  • C-c C-b p previous block
  • C-c C-b s block select

HTML element

  • C-c C-e / element close
  • C-c C-e a select element content
  • C-c C-e b element beginning
  • C-c C-e c element clone
  • C-c C-e d child element (down)
  • C-c C-e e element end
  • C-c C-e f toggle folding on children
  • C-c C-e i element insert
  • C-c C-e k element kill
  • C-c C-e m mute blanks between children
  • C-c C-e n next element
  • C-c C-e p previous element
  • C-c C-e r rename element
  • C-c C-e s select element
  • C-c C-e t transpose element
  • C-c C-e u parent element (up)
  • C-c C-e v element vanish
  • C-c C-e w wrap element

HTML tag

  • C-c C-t a sort attributes
  • C-c C-t b tag beginning
  • C-c C-t e tag end
  • C-c C-t m fetch matching tag
  • C-c C-t n next tag
  • C-c C-t p previous tag
  • C-c C-t s select tag

HTML attribute

  • C-c C-a b attribute beginning
  • C-c C-a e attribute end
  • C-c C-a i attribute insert
  • C-c C-a k attribute kill
  • C-c C-a n attribute next
  • C-c C-a p attribute previous
  • C-c C-a s attribute select
  • C-c C-a t attribute transpose

Helper functions

Engine families

Never forget to update the auto-mode-alist.
django twig, jinja, erlydtl, swig, liquid, selmer (Clojure), clabango, swig, nunjucks *.djhtml, *twig*, *.dtl, *.tmpl, *.njk
php *.php, *.psp, *.ctp php
erberuby, ember, erubis, embedded javascript*.erb, *.rhtml, *.ejs
velocitycheetah, vtl*.vsl, *.vtl, *.vm
gogtl*.gohtml, *.gotmpl
jsp*.jsp, *.gsp
aspx*.aspx, *.ascx
razorplay, play2*.cshtml, *.vbhtml
ctemplatemustache, handlebars, ember, hapax, ngtemplate, meteor, blaze, velvet*.hbs, *.mustache
template-toolkit*.tt, *.tt3



To report a bug, submit an issue on GitHub (note: a screenshot, an example on gist and the output of M-x web-mode-debug are always useful). It may also be a good idea to look at the *Messages* buffer.

For general questions (install, config, etc), use emacs.stackexchange.com (do not forget to associate the web-mode tag).


2018-XX : current/devel
2018-03 : v16 aka “Morfontaine” Release
  • engines: compatibility with SPIP (provided by @bystrano)
  • config: set web-mode-enable-optional-tags to t to accept certain unclosed elements (e.g. a <li> directly followed by a <li>)
  • twig: compatibility with {{ form_start() }} directive
  • closure: better indentation inside switch/case statements
  • riot: virtual script block compatibility (courtesy of @masnmt)
  • twig: better block uncommenting
  • apache server-side includes: indentation takes into account <!--#if > comments
  • jsx: compatibility with jsx fragments <></>
  • stylus: better indentation and fontification
  • comment annotation (e.g. JSDoc, PHPDoc): can be enabled with (setq web-mode-enable-comment-annotation t). Two faces (web-mode-annotation-tag-face and web-mode-annotation-type-face) have been added.
  • ruby: compatibility with <script type="text/ruby"> (Opal framework)
  • go templates: compatibility with {{- -}} blocks
  • twirl/scala: detection and fontification of 'symbols
  • twig: complex blocks are now detected properly (e.g. {{ form_widget(address, {'attr': {'class': 'form'}}) }} ; the first }} used to be detected as the end of the block
  • enable lexical binding
2017-05 : v15 aka “Verona” Release
  • engine: compatibility with archibus views (*.axvw) : indentation and fontification of <sql> elements
  • web-mode-auto-quote-style : 1 (default) for double quotes, 2 for single quotes
  • engine: compatibility with xoops, hero (go) template engines
  • dust: compatibility with text/x-dust-template scripts
  • jsx: attribute auto-closing (attr={})
  • indentation in Relay.QL Relay and gql, graphql GraphQL template literals
  • indentation in html template strings (useful for vue.js and angular scripts)
  • stylus: basic compatibility (cf. <style lang="stylus">)
  • js: nice perf improvement by Brian Malehorn
  • code indentation: 300% speed improvement
  • django: compatibility with {% javascript %} and {% stylesheet %} blocks (used by shopify for example)
  • folding: html comment folding
2016-05 : v14 aka “Boulogne-sur-mer” Release
  • jsx: better indentation for expressions
  • indentation: web-mode-indentation-params: new option case-extra-offset
  • indentation: web-mode-indentless-attributes (by default: onclick, onmouseover, onmouseout, onsubmit) and web-mode-indentless-elements (by default: <code>, <pre>, <textarea>)
  • indentation: improved behavior for code without semilicon
  • javascript: es6 arrow function fontification
  • html: single quote auto-pairing (for html attr values)
  • html: conditional comments (IE) indentation
  • xml: deal with with namespaced tags (e.g. <xs:element>)
  • tabs: web-mode-use-tabs must now be called manually
  • new engines: riot, marko
  • template-toolkit: new control blocks
  • fontitication: new face for namespaced tags (e.g. <c:forEach>): web-mode-html-tag-prefixed-face
  • comment: better behavior when commenting a multiline region (respect indentation)
2015-11 : v13 aka “Chantilly” Release
  • jsx: recursive parsing of the jsx expressions for better highlighting and indentation
  • jsx: compatibility with spread attributes
  • jsx: commenting with {/* */} blocks (+ uncommenting)
  • jsx: tag auto closing
  • jsx: less void elements (<Col></Col> are often used by jsx developers for example)
  • jsx: new custom: web-mode-jsx-expression-padding
  • indentation: (add-to-list 'web-mode-indentation-params '("lineup-ternary" . nil)) is now available
  • indentation: html comment aligning improvement (thx @thwg)
  • indentation: better behavior when the first attribute of an html element is on the next line
  • fontification: html entities can be fontified with (setq web-mode-enable-html-entities-fontification t) (customization can be done through web-mode-html-entity-face)
  • javascript: compatibility with splats
  • javascript: compatibility with arrow functions (indentation)
  • blade: compatibility with @yield blocks
  • handlebars: fontification fixes
  • jsp: the character . can be used as a tag name separator (it used to be only :)
  • compatibility with <script type="text/x-jquery-tmpl"> scripts
  • compatibility with *.tsx files (TypeScript JSX)
  • indentation: auto indentation on yanking
  • current element highlighting: only the tag name is highlighted
2015-07 : v12 aka “Paris” Release
  • mark-and-expand: new state when point is inside an html element ; a selection of the inner content is done before selecting the element.
  • mark-and-expand: preserve original vertical window offset
  • blade: compatibility with new blocks: {!! $x !!}
  • dust: better indentation for multiline blocks
  • django: improved distinction between keywords and control blocks (tags)
  • jinja: compatibility with {% trans %} {% endtrans %}, and {% for %} {% else %} {% endfor %} blocks (undefined in django)
  • mason: better fontification
  • erb: more accurate detection of control blocks
  • javascript: better lexing to avoid ambiguation between the / operator, /(a|b)/ regexp and comments (// and /* */)
  • unicode: better care of multibyte characters (thx kissge)
  • indentation: deal with javascript decorator syntax
  • web-mode-enable-sql-detection must be set to t to enable sql fontification and indentation in strings
  • fontification: improved compatibility with minor modes relying on font-locking (e.g. whitespace-mode)
  • fontification: new face web-mode-filter-face (e.g. “length” in {{ mylist|length }})
  • fontification: if web-mode-enable-part-face is t you can customize web-mode-script|style-face which inheritate from web-mode-part-face ; it can be used to set the default foreground/background of the part
  • minor mode: basic compatibility with hide show mode
  • indentation: customize string concat indentation with lineup-quotes
  • other: compatibility with universal-argument ( C-u ) for web-mode-element-clone|kill|next|previous|vanish, web-mode-attribute-kill|next|previous
2015-02 : v11 aka “Val d’Isère” Release
  • indentation: you can force attribute indentation inside html tag with web-mode-attr-indent-offset (by default, attributes are aligned on the first attr)
  • indentation: lineup (function arguments, cascaded calls, string concatenations) can be disabled with (add-to-list 'web-mode-indentation-params '("lineup-args|calls|concats" . nil))
  • indentation: better indentation for multi line tag attribute values
  • indentation: avoid extra indentation due to control blocks with (setq web-mode-enable-control-block-indentation nil)
  • indentation: web-mode better handles indent-tabs-mode (when this minor mode is detected, web-mode-use-tabs is called)
  • indentation: default indent offsets inherit the var 'standard-indent (when it is bound)
  • comments: for languages that accept alternative syntax for comments, you can use the var web-mode-comment-formats (e.g. (add-to-list 'web-mode-comment-formats '("php" . "//")))
  • var: web-mode-enable-auto-quoting to add double quotes after a = inside a tag
  • visual: less flickering while editing unclosed php block
  • feature: web-mode-enable-engine-detection : you can put a line like -*- engine: ENGINE_NAME -*- in a comment at the beginning of the template
  • expanders: typing d/s/ will expand to <div><span>|</span></div> (see web-mode-expanders). You can enable this feature with (setq web-mode-enable-auto-expanding t)
  • engines: compatibility with elixir (Erlang), thymeleaf, cl-emb (Lisp, thx Matthew Carter), heist
  • mako: compatibility with self-closed blocks (e.g. <%inherit file="base.html"/>)
  • erb: compatibility with <%= javascript_tag do %> js blocks
  • ejs: better compatibility
  • freemarker: many fixes and optimizations
  • helpers: web-mode-element-insert, web-mode-attribute-insert, web-mode-attribute-kill, web-mode-enable|disable (to enable/disable features like column highlighting, tag highlighting, space visualisation, etc.)
  • javascript: backtick strings compatibility
  • html: more html entities
  • WARNING: new format for auto-pairs and snippets (the character | is used to set the position of the cursor)
2014-10 : v10 aka “Cinque Terre” Release
  • speed: part partial invalidation (css/javascript/json), better attribute parsing (25% speedup when loading the html5 spec file)
  • responsiveness: region invalidation (web-mode-propertize) is now asynchronous, only triggered when needed (fontification, indentation)
  • helpers: web-mode-tag-attributes-sort, web-mode-surround
  • indentation: content inside <code>, <pre>, <textarea> is not indented any more ; (setq web-mode-pre-elements '()) if you don’t wan’t this feature
  • indentation: sql indentation inside block strings
  • html specs: single/double quoted attrs can now contains < and > characters
  • visual: less flickering while editing an unclosed tag/attribute/block
  • visual: you can enable inlays with (setq web-mode-enable-inlays t), at the moment it highlights LaTeX code in html content (e.g. \(\sqrt{3x-1}+(1+x)^2\))
  • javascript: ES6 javascript template strings are now highlighted (var s = "Hello ${ world }";)
  • visual: display column highlighting for current tag ((setq web-mode-enable-current-column-highlight t))
  • vars: alists web-mode-extra-constants|keywords|types replace web-mode-extra-php-constants, web-mode-extra-php-keywords, etc.
  • sexp: web-mode optimized sexp functions with (setq web-mode-enable-sexp-functions t)
  • minor modes: auto-complete compatibility (thx Nate Eagleson)
  • unit testing: see the directory unit-test and the function web-mode-test
  • content types: web-mode can edit scss and jsx files
  • visual: element content and tag name fontification: see web-mode-enable-element-content|tag-fontification and web-mode-element-content|tag-faces)
  • engines: compatibility with LSP (Lisp Server Page), Embedded JavaScript (EJS), Clip (Lisp)
  • erb: compatibility with <% case|when %> control blocks
  • asp: better tag compatibility
  • grails: <r:style> css parts
2014-05: v9 aka “IRCAM” Release
  • important milestone: compatibility with minor modes relying on font-locking
  • partial block invalidation is enabled by default: (setq web-mode-enable-block-partial-invalidation t): altering huge php/asp blocks is way faster
  • engine compatibility: web2py (python), mako (python), mason (perl), reactjs (jsx), blaze, mojolicious (perl)
  • compatibility with JSHint: web-mode-jshint
  • helpers: web-mode-element-mute-blanks (put comments between the children elements), web-mode-attribute-beginning|end|select|transpose
  • compatibility with js and css templates: e.g. *.js.erb or *.css.erb
  • compatibility with Grails (gsp) <r:script> javascript parts
  • backward|forward-sexp deals better with comments
  • indentation improvements: multiline statements (e.g. -> at the beginning of a php line), switch ... case
  • less flickering during html tag attribute highlight
  • asynchronous control blocks detection
  • faster undo
  • more robust string highlighting
  • alternative delimiters can be defined for smarty (see web-mode-engines-alternate-delimiters)
  • faces: web-mode-css-variable-face for CSS variables (--*), web-mode-html-attr-engine-face for engine specific attributes (e.g. ng-* for angularjs) (see also web-mode-engine-attr-regexps)
  • part and block auto opening
2014-01: v8 aka “Copenhagen” Release
  • element auto opening when hitting RET between collapsed start and end tags
  • web-mode-debug displays useful informations for debugging
  • web-mode-dom-normalize: change the case (lower-case for tags / attributes), convert entities, indent, smart parens (see web-mode-normalization-rules)
  • faces: it is now possible to customize the color of html tag brackets and of the equal character between the attr name and the attr value. See web-mode-html-tag-bracket-face and web-mode-html-attr-equal-face
    You can also choose a different color for custom elements (e.g. <polymer-element>) and custom attributes (e.g. data-*). See web-mode-html-tag-custom-face and web-mode-html-attr-custom-face
  • feature: highlight the current html element (see web-mode-enable-current-element-highlight and web-mode-current-element-highlight-face)
  • engine compatibility: template-toolkit (perl), liquid (jekyll), angular.js
  • asp: better indentation
  • razor: better indentation / fontification
  • html: compatibility with custom HTML elements (e.g. Google Polymer elements like <polymer-element>)
  • javascript: font-locking improvement, better regexp tokenizing str.replace(/"/gi,'"')
  • django: {% comment %}{% endcomment %} comments, fontification of “types”, compatibility with new blocks ({% verbatim %}, {% empty %})
  • erb: heredoc strings compatibility, var interpolation in strings, ruby-mode.el and web-mode.el font-locking are more consistent
  • php: var interpolation in strings, if / else statements without braces are indented
  • blade: compatibility with @section ... @stop syntax (@section ... @endsection is still supported)
  • helper functions: web-mode-block-kill , web-mode-block-select, web-mode-element-vanish (start tag and end tag are removed, but the content is kept), web-mode-element-wrap, web-mode-element-transpose, web-mode-element-children-fold-or-unfold
  • snippets / auto-pairing: auto-pairs and snippets are now specific to engines.
2013-09: v7 aka “Verteuil” Release
  • optimized region invalidation for asp and css
  • better asp indentation
  • compatibility with new engines: dustjs, underscore.js, closure (soy)
  • more core factorisation for indentation, block navigation and folding
  • new functions: web-mode-set-engine, web-mode-element-child, web-mode-dom-traverse (for a dom tree traversal), web-mode-xpath
  • specific menu in the menu-bar
  • block controls (e.g. <?php if ($x): ?> , {% if x %} ) are now considered when calculating markup indentation
  • left padding can be defined with web-mode-style-padding (default: 1), web-mode-script-padding (default: 1), web-mode-block-padding (default: 0)
  • better behavior of cursor jumping when closing an HTML element
  • web-mode-enable-comment-keywords can be used with web-mode-comment-keywords to highlight keywords in comments (with web-mode-comment-keyword-face face)
  • shortcuts now comply with emacs rules (C-c letter is reserved)
  • web-mode-tag-end, web-mode-element-end move point after >
2013-06: v6 aka “Roma” Release
  • complete rewrite of the indentation code: web-mode.el can be used to edit plain php, css or javascript code (see web-mode-content-types-alist)
  • compatibility with <script type="application/ld+json">
  • faster, smaller and more robust indentation function for code (php, js, etc.)
  • web-mode-enable-block-faces
  • web-mode-enable-heredoc-fontification
  • 30% speedup when editing http://www.w3.org/html/wg/drafts/html/master/
  • compatibility with the Go Template Engine, vbscript asp
  • yasnippets are indented (thx Lincoln de Sousa for the advice)
  • helpers: web-mode-xml-replace
  • compatibility with erlydtl
  • imenu first experiment
  • css colorization : foreground always readable (thx @tadfisher)
  • more robust code for tag/element navigation
  • better switch case indentation
  • indent html content with (setq web-mode-indent-style 2)
  • basic compatibility with fill-paragraph M-q : handles comments and html text
2013-03: v5 aka “Tignes” Release
  • show tag mismatched
  • new helper functions: web-mode-(entities|quotes)-replace
  • compatibility with the Blade template engine (laravel framework)
  • invalid whitespaces display
  • compatibility with <script type="text/html"> useful for knockoutjs for example (thx Anatoly Kudinov)
  • code refactoring (with dolist and dotimes) (thx aiwass93)
  • new helper functions: web-mode-server-block-beg-pos, web-mode-server-block-end-pos, web-mode-prev-server-block-pos, web-mode-next-server-block-pos, web-mode-prev-server-block, web-mode-next-server-block
  • avoid dependency on cl (thx Ivan Toshkov)
  • compatibility with new engines: CTemplate/Mustache/Handlebars/Meteor/ember.js
  • better compatbility with emacs 23 (thank you @Archenoth @almost @johnjcamilleri @NateEag)
  • more extensions recognized (html.twig)
  • django fixes (thx vigneshsarma)
2013-01: v4 aka “Trianon Palace” Release
  • compatibility with new engines: velocity/cheetah, smarty
  • compatibility with the alternative FreeMarker syntax: [# ] [@ ] (thx Daniel Dekany)
  • indentation improvement (thx gnuvince)
  • speed improvement when using web-mode-engines-alist
2012-12: v3.5 aka “St Petersbourg” Release
  • compatibility with the PHP short tags syntax (adapted from Etenil patch)
  • speed optimizations with a better identification of the region to invalidate
  • compatibility with a new template engine: freemarker
  • bug fixes (thanks T Parslow)
  • huge indentation speed up
2012-11: v3 aka “Sandy at the Knickerbocker Club” Release
  • block identification (language, type, side) is done outside of font-locking. This code refactoring (and well as many other improvements) has been done as result of a code review done by Stefan Monnier (thank you Stefan). This loop also identifies blocks (style, script, php etc. setting text-properties like server-side, client-side etc.)
  • compatibility with yasnippet (thx Joao)
  • html5 aware (cf. new void tags)
  • better handling of html attributes (unquoted, single/double quoted)
  • php here|nowdoc syntax compatibility (for string identification)
  • customization of indentation style with the var web-mode-indent-style: 1=default, 2=leftish
  • new var : web-mode-tag-autocomplete-style. With the value 2, inserting a > triggers auto closing of the element
  • more folding (e.g. {% for | if | while %} {% endfor | endif | endwhile %})
  • <tag></tag> : cursor jumps between open and closing tag (thx analysis918)
  • new templating engines compatibility : twig (symfony), jinja, django, liquidmarkup (ruby)
  • css colorization (similar to rainbow-mode.el, thx Julien Danjou for your answers)
  • C-c C-m mark and expand (similar to expand-region.el, thx Vmalloc and Magnars)
2012-09: v2 aka “La Baule” Release
  • basic ASP compatibility
  • code folding
  • compatibility with pure PHP scripts (closing ?> is not required)
  • M-; web-mode-comment-uncomment (according to (point), the region and to the block type)
  • font-lock-multiline is nil
  • edition of *.css file
  • many CSS improvements
  • Ruby (erb) basic support
2012-05: v1 aka “Valençay” Release
  • JSP basic support
  • better syntax for snippets
  • web-mode-element-at-point (used by web-mode-is-opened-element)
  • defface
  • syntax coloring for css and js
  • web-replace-apostrophe
  • indentation for php array and html attributes
  • snippets
  • html tag auto closing
  • html tag navigation
2011-08: v0 aka “La Ferté-Vidame” Release
  • first experiments


Ivan Toshkov, Guillaume Pasquet, Andrei Chitu, Jose Peleteiro, Tad Fisher, Lee SanGu, Igor Shymko, Teruki Shigitani, Ed Slocomb, Adam Sokolnicki, Haijun Yang, Mitchel Humpherys, Brent Carmer, Bin Chen, Nate Eagleson, Matthew Carter, Stephen Whipple, Olexandr Sydorchuk, Derrick Higgins, Tomoki Ohno, Brian Malehorn


François-Xavier Bois - < fxbois AT Google Mail Service >

Founder and CTO of Kernix, Digital Agency (Web/Mobile) in Paris (FR).

@fxbois -

web-mode.el has been presented to the 7th European Lisp Symposium with the paper: Heterogeneous recursive code parsing with Emacs Lisp.

Friendly links: scopalto, magazines gratuits au format pdf

Donate using PayPal
Donate using Liberapay
long board brand
top tool advisor