Educational guide to document semantics selectors
Select what the document means
Compare language, direction, definition, and heading levels without confusing attributes, styles, and computed semantics.
Mental model
These pseudo-classes query information that the browser obtains from the document; the visible attribute does not always tell the whole story.
:lang() · :dir()Effective values
:lang() can match through inheritance or a regional variant. :dir() queries inherited semantic direction or the direction calculated by dir="auto", not the CSS direction property.
:definedA state that really changes
Built-in HTML elements are already defined. A custom element becomes :defined when registered; that name cannot be unregistered, so restart uses another one.
:heading · :heading()Semantic heading
:heading selects elements with a heading level; :heading() filters integer levels, including several separated by commas. Support is still experimental.
headingoffset · headingreset · aria-levelHTML level, tag, and ARIA
HTML can offset or reset the level with headingoffset and headingreset, so the level can differ from h1–h6. aria-level expresses accessibility semantics but does not by itself make a div match these selectors. Safari 27.2 still selected an offset h1 as level 1; the demo uses native headings without offsets.
Language and direction
Compare the effective semantic value with the attributes written on each element.
:lang()
:lang(<language-range>)Elements whose effective language matches the specified range.
Specificity(0, 1, 0)
:lang()
:lang(<language-range>)Behavior
- Language can be inherited from an ancestor even when the element has no lang attribute.
- :lang(fr) also includes regional variants such as fr-CA; an explicit lang="en" interrupts that inheritance.
Examples
:lang(fr)Matches: <section lang="fr">
Matches: <p>Bonjour</p>
<p lang="en">Hello</p>
</section>:lang(fr)Matches: <div lang="fr-CA">
Matches: <p>Salut</p>
</div>Common mistakes
- It is not equivalent to [lang="fr"], which compares only an attribute on the element itself.
- Visible text does not determine language; the language assigned to the element does.
:dir()
:dir(ltr | rtl)Elements with the requested effective semantic direction.
Specificity(0, 1, 0)
:dir()
:dir(ltr | rtl)Behavior
- A descendant can inherit rtl without having dir="rtl" on its own tag.
- dir="auto" calculates direction from content; the Arabic example text gives rtl.
Examples
:dir(rtl)Matches: <div dir="rtl">
Matches: <p>مرحبا</p>
<p dir="ltr">Hello</p>
</div>:dir(rtl)<div dir="ltr">
Matches: <p dir="auto">مرحبا</p>
<p dir="auto">Hello</p>
</div>Common mistakes
- It is not equivalent to [dir="rtl"], which finds only explicit attributes.
- The CSS direction property changes presentation but not the result of :dir().
Element definition
Observe the difference between built-in HTML and a custom element before and after registration.
:defined
Built-in elements and custom elements that already have a registered definition.
Specificity(0, 1, 0)
:defined
Behavior
- A built-in HTML button matches from the start; an autonomous custom element starts undefined.
- After customElements.define(), the same node starts matching. The second example represents that later moment.
Examples
button:defined<div>
Matches: <button type="button">Built-in button</button>
<demo-card>Waiting</demo-card>
</div>demo-card:defined<!-- after customElements.define("demo-card", ...) -->
Matches: <demo-card>Defined</demo-card>Common mistakes
- It does not mean the element is valid, visible, or finished loading its content.
- customElements.define() cannot be undone for the same name: the demo restarts with a new one.
Headings and levels
Examples show the intent of Selectors Level 5; the playground counts matches only when this browser produces them natively.
:heading
Elements with a heading level according to the document language’s semantics.
Specificity(0, 1, 0)
:heading
Behavior
- The form without parentheses covers all heading levels and contributes (0, 1, 0) specificity.
- A div with role="heading" and aria-level did not pass observed native matching; the demo uses h1–h3.
Examples
:heading<section>
Matches: <h1>Main heading</h1>
Matches: <h2>Section heading</h2>
<p>Content</p>
</section>:heading<section>
<div role="heading" aria-level="2">Text with an ARIA heading role</div>
Matches: <h3>Subsection heading</h3>
</section>Common mistakes
- Do not assume an ARIA role makes an arbitrary element match :heading.
- Do not mistake parseable syntax for CSS and DOM matching support.
Support and compatibility
September 25, 2026 test: Safari 27.2 matched; Firefox 156, even with the preference enabled, and Chrome 153 rejected this form. The playground requires a positive native match.
:heading()
:heading(<integer>#)Headings whose semantic level belongs to the specified list of integers.
Specificity(0, 1, 0)
:heading()
:heading(<integer>#)Behavior
- :heading(2) filters level 2; :heading(1, 2) accepts several levels in one functional form.
- The pseudo-class contributes (0, 1, 0); adding a type such as h2 also contributes (0, 0, 1).
Examples
:heading(2)<section>
<h1>Main heading</h1>
Matches: <h2>Section heading</h2>
<h3>Subsection heading</h3>
</section>:heading(1, 2)<article>
Matches: <h1>Main heading</h1>
Matches: <h2>Section heading</h2>
<h3>Subsection heading</h3>
</article>Common mistakes
- An empty list or names such as :heading(foo) are invalid: integers are required.
- HTML defines offset levels with headingoffset, but Safari 27.2 did not apply them to observed matching. The demo does not simulate this case.
Support and compatibility
September 25, 2026 test: Safari 27.2 matched :heading(2); Firefox 156 with the preference and Chrome 153 rejected it. The playground probes each chosen level with a native heading.