HTML5-Friendly Markup

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Jakarta Faces 4.1 (API jakarta.faces-api 4.1.2, part of Jakarta EE 11), Eclipse Mojarra 4.1.17 and Apache MyFaces Core 4.1.4. Faces 4.0 behaves the same except where the text says otherwise. The behavior is taken from the specification, the Jakarta EE Tutorial and the VDL, and every example was run on 2026-10-08 in a scratch Bookshelf WAR on Tomcat 11.0.26 with Weld 6.0.4.Final, once with Mojarra 4.1.17 and once with MyFaces 4.1.4, and requested with curl. The rendered output shown is captured from those runs; the order of the attributes differs between the two implementations, and the jsessionid and view state values are omitted.

The component model of Faces hides HTML behind components: h:inputText decides which <input> element and which attributes reach the browser. HTML5 added input types (email, date, number), attributes (placeholder, autofocus, pattern) and whole elements (<meter>, <details>) that the standard components did not know. Faces 2.2 answered with the HTML5-friendly markup feature: instead of new components that imitate HTML5, the view author may write HTML5 directly and still get the server-side component behind it. It has two halves, which are mirror images of each other.

Half You write You get

Pass-through attributes

A Faces component tag with extra attributes the component does not know.

The component and renderer you chose, with the extra attributes copied to the browser untouched.

Pass-through elements

A plain HTML element with at least one attribute from the Faces namespace.

The HTML you wrote, backed by a real server-side component that takes part in the lifecycle.

Namespaces

Both halves are switched on by XML namespaces on the root element. Jakarta Faces 4.0 renamed them from the http://xmlns.jcp.org/... URIs to short jakarta.faces... URNs.

Purpose Faces 4.0 and later Faces 2.2 and 3.x Usual prefix

Pass-through attributes

jakarta.faces.passthrough

http://xmlns.jcp.org/jsf/passthrough

p or pt

Pass-through elements (attributes on plain HTML)

jakarta.faces

http://xmlns.jcp.org/jsf

faces (earlier jsf)

Standard components

jakarta.faces.html, jakarta.faces.core

http://xmlns.jcp.org/jsf/html, http://xmlns.jcp.org/jsf/core

h, f

The old URIs still work in 4.x but are planned for removal in Faces 5.0, so new pages should use the URNs. A page that uses both halves starts like this (the HTML5 <!DOCTYPE html> is plain and Facelets accepts it):

Root element of an HTML5 Facelets page
<!DOCTYPE html>
<html lang="en"
      xmlns:faces="jakarta.faces"
      xmlns:f="jakarta.faces.core"
      xmlns:h="jakarta.faces.html"
      xmlns:p="jakarta.faces.passthrough">

A page that declared the old jsf, core, html and passthrough URIs of Faces 2.2 and 3.x still produced a pass-through element (jsf:id, jsf:value) and a pass-through attribute (p:type) on Mojarra 4.1.17 and MyFaces 4.1.4. The other namespaces are explained on the Facelets page.

Pass-through Attributes

For a component tag, the attributes it supports are fixed by its component and renderer. Some are interpreted (the columns attribute of h:panelGrid), some are copied to the output (the lang attribute of h:inputText). Anything else used to be dropped or ignored. A pass-through attribute is a name and value that the renderer copies to the browser without interpreting it. The specification offers three ways to declare one.

Syntax Use it when

p:name="value" on the component tag

The attribute is fixed in the page; this is the common form.

<f:passThroughAttribute name="n" value="v"/> nested in the component

The attribute name or value is computed, or you want to add a single attribute conditionally with rendered-style EL.

<f:passThroughAttributes value="#{map}"/> nested in the component

A whole group of attributes comes from a bean. The EL expression must evaluate to a Map<String, Object>; each value has toString() called on it.

The next input for the Bookshelf review form uses all three styles. The first two are equivalent; the third supplies the attributes from a map.

Three ways to add the same attributes
<!-- 1. namespace prefix -->
<h:inputText id="rating" value="#{reviewForm.rating}"
             p:type="number" p:min="1" p:max="5"
             p:title="Enter a rating from 1 to 5."/>

<!-- 2. nested single attributes -->
<h:inputText id="rating2" value="#{reviewForm.rating}">
  <f:passThroughAttribute name="type" value="number"/>
  <f:passThroughAttribute name="min" value="1"/>
  <f:passThroughAttribute name="max" value="5"/>
</h:inputText>

<!-- 3. a Map<String, Object> from a bean -->
<h:inputText id="rating3" value="#{reviewForm.rating}">
  <f:passThroughAttributes value="#{reviewForm.ratingAttributes}"/>
</h:inputText>

Each renders, on Mojarra, <input id="reviewForm:rating" name="reviewForm:rating" value="3" type="number" min="1" max="5" title="Enter a rating from 1 to 5." /> (the form had the id reviewForm; the second and third inputs lack the title); MyFaces wrote the same attributes in another order. The type="number" replaces the default type="text" that h:inputText would otherwise write, which is the usual reason to use a pass-through attribute: pick a better HTML5 input type while keeping the server-side conversion, validation and value binding of h:inputText. Pass-through attribute values may use EL, as the specification’s pt:autofocus="#{config.autofocus}" example shows: with p:autofocus="#{reviewForm.autofocus}" and a boolean true, both implementations wrote autofocus="true". The name, however, is always a literal.

Because the browser, not Faces, interprets these attributes, the Faces behavior does not change. An HTML5 min and max are checks the browser makes before it submits; they are not validators. The server still needs f:validateLongRange or Bean Validation, as a request can be forged, as the lifecycle page explains. The validators and converters are covered on the validators and converters pages.

Pass-through Elements

The converse: you write an HTML element and mark it as a Faces element by giving it at least one attribute from the jakarta.faces namespace. Facelets then replaces it, during view build, with the matching standard component, but the output is the markup you wrote. The most useful marker is faces:id, as it gives the component the identifier that postback processing, validation messages and Ajax need.

A pass-through element in place of h:inputText
<input type="email" faces:id="email" faces:value="#{reviewForm.email}"
       required="required" placeholder="you@example.org"/>

Here the <input> is an h:inputText in the component tree (the type="email" selects it) with the HTML you typed as its output. The value binding is written faces:value, not value: the attributes in the faces namespace become the attributes of the component, and every other attribute is a pass-through attribute. A plain value="#{reviewForm.email}" next to faces:id rendered the element but never bound it: in the run, a submitted address did not reach the bean on either implementation, while with faces:value the bean property was set (email=x@y.org) and the value was written back to the field. Other faces: attributes also trigger the transformation and pick the component; faces:action on an <a> makes an h:commandLink and faces:outcome on an <a> makes an h:link. The element name plus the identifying attribute selects the component:

HTML element Identifying attribute Component

a

faces:action or faces:actionListener

h:commandLink

a

faces:value

h:outputLink

a

faces:outcome

h:link

button

none

h:commandButton

button

faces:outcome

h:button

body, head, form

none

h:body, h:head, h:form

img, label

none

h:graphicImage, h:outputLabel

link, script

none

h:outputStylesheet, h:outputScript

input

type="button", "submit", "reset"

h:commandButton

input

type="checkbox"

h:selectBooleanCheckbox

input

type="file", "hidden", "password"

h:inputFile, h:inputHidden, h:inputSecret

input

type of color, date, datetime, datetime-local, email, month, number, range, search, time, url, week or any other

h:inputText

select

none, or multiple

h:selectOneListbox, h:selectManyListbox

textarea

none

h:inputTextarea

Source: the table "How Facelets Renders HTML5 Elements" in the Jakarta EE Tutorial section named above. The rows for a with faces:action, button with faces:outcome and input of type email were run on both implementations and produced a command link, a button that navigates to the outcome and an input component; a meter with faces:id rendered as a meter.

An element that is not in the table, such as <meter>, still works: with a faces: attribute it is turned into a generic element component (faces:element in the tag library, rendered by the jakarta.faces.passthrough.Element renderer), so the specification’s example <meter faces:id="meter" min="#{bean.min}" max="#{bean.max}" value="350"> is a valid view. In every case you keep the lifecycle: the component is in the tree, receives its submitted value, is converted and validated, and can carry f:ajax, f:validateLength or f:convertNumber children.

Without any faces: attribute an <input> is ordinary template text, not a component. It is written to the output as is and never takes part in the lifecycle, so a typed value is not bound to the bean. In the run, <input type="text" id="plain" name="plain" value="stays template text"/> came out unchanged on both implementations, next to the converted elements. This is the cause of the common question "my HTML form does not reach the backing bean". Add faces:id and faces:value="#{...}" to bind it.

The name you give an element is not safe to rely on. The element <input type="email" faces:id="email" name="email" .../> inside <form faces:id="reviewForm"> was rendered on Mojarra as name="reviewForm:email", the client identifier that decoding looks for, so the hand-written name was replaced. MyFaces kept the hand-written name="email", and a postback with the field email did not set the bean property, as did one with reviewForm:email. So leave out name on an element that carries a faces: attribute, and let the renderer write it.

Choose between the two halves by who owns the markup. With pass-through attributes the component and renderer own it and you decorate. With pass-through elements you own it, including the closing of the form and the HTML name attribute; the renderer fills in the component identifier. A designer who hands you finished HTML5 will prefer elements; a team that already uses h: tags everywhere will add p: attributes.

The type Attribute of h:inputText

Before Faces 4.0 the only way to get type="email" or type="date" out of h:inputText was p:type or a pass-through element. The VDL of Faces 4.0 and 4.1 lists a type attribute on h:inputText itself (the 3.0 VDL does not): "Controls the data type and associated control of the element", with the default text. The VDL adds that when the value names a type for which a more specific component exists (hidden, password, checkbox, radio, file, submit, image, reset and button) and the project stage is Development, the renderer must add a warning that the more specific component should be preferred. So the common case is plain.

The type attribute (Faces 4.0 and later)
<h:inputText id="published" type="date" value="#{reviewForm.published}">
  <f:convertDateTime type="localDate" pattern="yyyy-MM-dd"/>
</h:inputText>
<h:inputText id="email"     type="email"   value="#{reviewForm.email}"/>
<h:inputText id="pages"     type="number"  value="#{reviewForm.pages}"/>
<h:inputText id="nodefault" value="#{reviewForm.email}"/>

Both implementations rendered, with the bean’s date 1965-08-01 and 412 pages:

<input id="reviewForm:published" type="date" name="reviewForm:published" value="1965-08-01" />
<input id="reviewForm:email" type="email" name="reviewForm:email" value="" />
<input id="reviewForm:pages" type="number" name="reviewForm:pages" value="412" />
<input id="reviewForm:nodefault" type="text" name="reviewForm:nodefault" value="" />

(The attribute order and the placement of name differ slightly on MyFaces.) This is only the rendered HTML type; the conversion still comes from the bound property or from f:convertDateTime / f:convertNumber. The VDL of f:convertDateTime accepts type="localDate" among the java.time values, and the run confirms the advice for dates: a type="date" browser control submits yyyy-MM-dd, and the converter above parsed 1966-01-02 into the LocalDate property on both implementations. Without the converter the same submission failed, in different ways: Mojarra reported a conversion error with null Converter in its message, and MyFaces answered with a 500 whose cause was Cannot convert [1966-01-02] of type [class java.lang.String] to [class java.time.LocalDate]. A text that does not match the pattern (01/02/1966) was reported on both as could not be understood as a date. On a Faces 2.2 or 3.x server use p:type="date".

placeholder, data-* and Other HTML5 Attributes

Every HTML5 attribute that a Faces tag does not support is a pass-through attribute. The ones that come up most often:

<h:inputText id="isbn" value="#{reviewForm.isbn}"
             p:placeholder="978-..."
             p:autofocus="autofocus"
             p:autocomplete="off"
             p:pattern="[0-9\-]{10,17}"
             p:inputmode="numeric"
             p:data-book-id="#{reviewForm.bookId}"/>

Both implementations wrote every attribute, with the EL value resolved:

<input id="reviewForm:isbn" type="text" name="reviewForm:isbn" value="" placeholder="978-..." autofocus="autofocus" autocomplete="off" pattern="[0-9\-]{10,17}" inputmode="numeric" data-book-id="42" />
  • p:placeholder gives a hint inside the empty field. Do not use it as the only label: it disappears while the user types.

  • p:autofocus and p:autocomplete are the HTML5 attributes of the same name, passed to the browser as written.

  • p:pattern, p:min, p:max, p:step, p:maxlength and p:required are browser-side validation. The Faces required="true" is the server-side one; set both to get fast feedback and safe validation.

  • p:data-* attributes carry data to JavaScript, for instance p:data-book-id. The name is a literal that may contain hyphens; the value is an EL expression evaluated when the view renders.

  • A form with p:novalidate="novalidate" switches the browser’s built-in validation off, which is useful when you want Faces messages to be the only ones shown. On h:form both implementations rendered novalidate="novalidate" on the form element.

Accessibility Attributes

Accessibility attributes are HTML5 attributes, so they are pass-through attributes. The standard components already carry some of them and the rest come through p:.

Need Markup

A visible label tied to the input

<h:outputLabel for="email" value="Email"/> renders <label for="...">, and Faces expands for to the client identifier, including the form prefix. This is better than a placeholder.

An accessible name when there is no visible label

p:aria-label="Search the catalogue"

The relation to a message or help text

p:aria-describedby="email-help" on the input, with the target element’s id written literally. A component’s id becomes a client identifier such as reviewForm:emailMsg, so a pass-through attribute that refers to a component must use that full value (p:aria-describedby="reviewForm:emailMsg").

Required, invalid, live regions

p:aria-required="true" on the input, and p:role="alert" or p:aria-live="polite" on h:message or h:messages so a screen reader announces validation errors.

Meaningful images

<h:graphicImage name="cover.png" alt="Cover of ..."/>; alt is a native attribute, an empty alt="" marks a decorative image.

Page language

lang="en" on <html>; it is plain text in the template.

A form that used them, run on both implementations:

An accessible review field
<h:form id="reviewForm" p:novalidate="novalidate">
  <h:outputLabel for="email" value="Email"/>
  <h:inputText id="email" value="#{reviewForm.email}" required="true" p:type="email"
               p:aria-required="true" p:aria-describedby="reviewForm:emailMsg"/>
  <h:message id="emailMsg" for="email" p:role="alert" p:aria-live="polite"/>
  <h:inputText id="search" value="#{reviewForm.isbn}" p:aria-label="Search the catalogue"/>
  <h:graphicImage name="cover.png" alt="Cover of Dune"/>
</h:form>

rendered (Mojarra, the attribute order differing on MyFaces):

<label for="reviewForm:email">Email</label>
<input id="reviewForm:email" name="reviewForm:email" value="" type="email" aria-required="true" aria-describedby="reviewForm:emailMsg" />
<span id="reviewForm:emailMsg" role="alert" aria-live="polite"></span>
<input id="reviewForm:search" type="text" name="reviewForm:search" value="" aria-label="Search the catalogue" />
<img src="RES_NOT_FOUND" alt="Cover of Dune" />

The for was expanded to the client identifier, the ARIA attributes arrived as written, and the span of the empty h:message was rendered with its role because it carries attributes. (The RES_NOT_FOUND src is only because the scratch application has no cover.png resource; Mojarra added a warning about it in the Development stage.) The Accessibility page covers the wider topic. This page only shows that Faces does not stand in the way: every ARIA attribute is one p: prefix away. Whether the resulting page is accessible still has to be checked in the browser with a screen reader or an audit tool.

The jsfc Attribute: the Historical Precursor

Pass-through elements were not the first attempt at letting designers keep their HTML. Facelets 1.x, the standalone view framework that predates JSF 2.0 and is described by Aranda and Wadia in Facelets Essentials, had the jsfc attribute. Its value names the component tag that should replace the element:

Facelets 1.x jsfc (historical)
<input type="text" jsfc="h:inputText" value="#{user.name}"/>
<a href="#" jsfc="h:commandLink" action="#{user.save}">Save</a>

The page remains valid, renderable HTML in a designer’s tool, while Facelets compiles it into the component. That advantage, mock-up friendliness, is the same one pass-through elements give. The differences explain why jsfc was superseded:

Aspect jsfc (Facelets 1.x) Pass-through elements (Faces 2.2 and later)

Which component

Named by the author: jsfc="h:inputText".

Inferred from the element and an identifying attribute, according to the table above.

Specified by

A Facelets library feature, not part of the Faces specification.

The Faces specification, in the Facelets TagDecorator API.

Output

The component’s renderer decides. In the run below, class of the input survived and the href="#" of the link was replaced by the renderer’s own.

The author’s markup is the output; the component is there for the lifecycle.

Status

Legacy.

Standard.

It is still accepted: this page

<form jsfc="h:form" id="reviewForm">
<input type="text" jsfc="h:inputText" id="isbn" value="#{reviewForm.isbn}" class="x"/>
<a href="#" jsfc="h:commandLink" action="#{reviewForm.save}">Save</a>
</form>

rendered on Mojarra 4.1.17 and MyFaces 4.1.4 as a form with an h:inputText (id="reviewForm:isbn", class="x") and a command link, and neither response contained the jsfc attribute. That is a legacy compatibility path and is not mentioned in the specification’s view-authoring chapter, so do not use jsfc in new code. When migrating a Facelets 1.x page, replace each jsfc="h:inputText" element by a faces:id element or by the h: tag with p: attributes. Most of the book is otherwise out of date for JSF 2.2 and later, as it describes standalone Facelets with its own ViewHandler. The templating page lists the rest of what changed since the book.

Source: Bruno Aranda and Zubin Wadia, Facelets Essentials (Apress, 2008, ISBN 978-1-4302-1049-8), and a scratch run of the page above on both implementations.

What Changed Between Versions

Version Change relevant to HTML5-friendly markup

JSF 2.2

HTML5-friendly markup is introduced: pass-through attributes (http://xmlns.jcp.org/jsf/passthrough, f:passThroughAttribute, f:passThroughAttributes) and pass-through elements (http://xmlns.jcp.org/jsf, the jsf: prefix).

Faces 3.0

The javax to jakarta rename; the namespaces keep the xmlns.jcp.org URIs.

Faces 4.0

The namespaces become jakarta.faces.passthrough and jakarta.faces (the conventional prefix of the second changes from jsf to faces); h:inputText gets the type attribute (listed in the 4.0 VDL, absent from the 3.0 VDL).

Faces 4.1

No change to this feature.

Faces 5.0 (upcoming, milestone 5.0.0-M9)

Planned: removal of the old XML namespaces (issue 2105 on the milestone page). Not current behaviour.

Where the Books Are Outdated

The books consulted for this section predate HTML5-friendly markup or predate its current form.

  • Aranda and Wadia, Facelets Essentials (2008). Describes the jsfc attribute as the way to keep HTML in a designer’s tool (see The jsfc Attribute: the Historical Precursor). Pass-through elements are the standard replacement.

  • Burns and Schalk, JavaServer Faces 2.0: The Complete Reference (2010) and Leonard, JSF 2.0 Cookbook (2010). Both describe JSF 2.0, which predates both pass-through attributes and pass-through elements (JSF 2.2). The h:inputText attribute type is newer still (Faces 4.0).

  • The PrimeFaces books. Use the p: prefix for the PrimeFaces tag library (http://primefaces.org/ui). Give the pass-through namespace another prefix, as the namespaces section of the Facelets page explains.

Pitfalls

  • Namespaces mixed up. p: must be bound to jakarta.faces.passthrough and faces: to jakarta.faces. A prefix bound to the wrong URI is not a pass-through namespace, and the attribute is not treated as one; the old jcp URIs are planned for removal in Faces 5.0.

  • Ids. On a pass-through element the HTML id attribute and faces:id are different. Use faces:id and let Faces produce the id and name; on MyFaces a hand-written name is kept and prevents postback binding.

  • value without the prefix. On a pass-through element a bare value="#{bean.x}" is a pass-through attribute, not the value of the component. Write faces:value.

  • A required HTML attribute and an empty submission. required="required" in the browser stops an empty submit before Faces sees it; if you want the Faces message, add novalidate or use only the Faces required.

  • Expression of the attribute name. Names cannot be computed on a p: attribute. Use f:passThroughAttribute or f:passThroughAttributes when the name varies.

  • Server validation. A pass-through min, max, pattern or maxlength is advice to the browser. Validate on the server too.

  • XML well-formedness. The page is XML. Attributes with no value (required, autofocus) must be written required="required"; a bare attribute is a parse error.