Templates and apply-templates

The processing model behind XSLT: template rules matched against nodes, apply-templates driving traversal, and the built-in rules that fill the gaps.

A stylesheet is a set of rules

XSLT is not a script that loops over a document. A stylesheet is a collection of template rules, and the processor walks the source tree, choosing the best-matching rule for each node. You influence the walk with xsl:apply-templates, which is the only way to move downward.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="html" indent="yes"/>

  <xsl:template match="/">
    <html>
      <body>
        <h1>Catalog</h1>
        <ul>
          <xsl:apply-templates select="catalog/book"/>
        </ul>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="book">
    <li>
      <xsl:value-of select="title"/>
      <xsl:text> - </xsl:text>
      <xsl:value-of select="price"/>
    </li>
  </xsl:template>
</xsl:stylesheet>
  • match decides which nodes a template handles; select decides which nodes get processed.
  • Literal result elements — plain HTML in the template — are copied to the output; only xsl: elements are instructions.
  • xsl:text preserves whitespace exactly, which matters when the stylesheet is indented.
  • xsl:value-of emits the string value of the first node in the selection — use xsl:for-each or another apply-templates for all of them.
  • Stylesheet whitespace inside a text-only template is stripped, which is why output can lose the spaces you expected.
💡
XSLT is declarative and functional: templates do not return values, they emit output as the processor visits nodes. Writing it like a procedural language produces stylesheets that fight their own engine.

Built-in rules and modes

If no explicit rule matches, seven built-in rules apply. The two that matter: a template for the root and for elements applies templates to children, and a template for text and attributes copies the value out. That is why a stylesheet with a single rule can still produce readable output.

NodeBuilt-in behaviour
Root and elementsApply templates to children
Text and attributesCopy the string value to the output
Comments and processing instructionsNothing
Namespace nodesNothing
<!-- suppress a subtree by shadowing the built-in rule -->
<xsl:template match="internal-notes"/>

<!-- process the same node differently in two passes -->
<xsl:template match="book" mode="summary">
  <xsl:value-of select="title"/>
</xsl:template>

<xsl:template match="book" mode="detail">
  <dl>
    <dt>Price</dt><dd><xsl:value-of select="price"/></dd>
    <dt>Id</dt><dd><xsl:value-of select="@id"/></dd>
  </dl>
</xsl:template>

<!-- call them by name with a mode -->
<xsl:apply-templates select="book" mode="summary"/>
<xsl:apply-templates select="book[@featured]" mode="detail"/>
  • An empty template body is the idiomatic way to delete nodes from the output.
  • Modes let one node have several presentations without duplicating match patterns.
  • xsl:call-template with xsl:with-param is the escape hatch when you need a subroutine rather than a rule.
  • Parameters declared with xsl:param can be overridden from the command line, which makes stylesheets reusable.

Running a transformation

# libxslt (XSLT 1.0)
xsltproc --output out.html catalog.xsl catalog.xml

# pass a parameter into the stylesheet
xsltproc --stringparam title "Q3 Catalog" catalog.xsl catalog.xml

# Saxon-HE runs XSLT 3.0 from the command line
java -jar saxon-he.jar -s:catalog.xml -xsl:catalog.xsl -o:out.html

# sanity-check both inputs first
xmllint --noout catalog.xml && xmllint --noout catalog.xsl

Two XSLT versions matter in practice: 1.0, which every browser and libxslt implements, and 2.0 or 3.0, which adds sequences, xsl:for-each-group, regular expressions and typed comparisons. Check which engine you have before using modern syntax.

FAQ

Why does nothing appear in the output?
Usually no rule matches, so only built-in behaviour runs and text is copied raw — or the root template exists but never calls apply-templates. Add <xsl:apply-templates/> inside the root template.
Can I still use XSLT in a browser?
Chrome removed it; Firefox and Safari support XSLT 1.0 through a stylesheet processing instruction. Treat browser XSLT as legacy and transform server-side.

Advanced matching and sorting XPath in XSLT and in code

Last refreshed 2026-09-18.