HTML Classes
html classes the class attribute groups styling and scripting hooks across elements without s the class attribute assigns space-separated tokens
Introduction
The class attribute assigns space-separated tokens for CSS, JS, and testing — without conveying semantics to assistive technology. Design systems at Shopify (Polaris) and Google (Material) map BEM-like classes to components while keeping semantics in tags. Staff rule: class names describe purpose, not appearance — class="error" not class="red-text".
Business problem
Class chaos — utility-class strings 200 chars long, duplicate class names across micro-frontends, and presentation classes used as AT hooks — creates unmaintainable CSS specificity wars and false confidence in accessibility.
- Collisions: Two teams ship .btn-primary — styles bleed across micro-frontends.
- A11y falsehood: .sr-only forgotten in dark mode override — screen reader text visible.
- SEO myth: Keyword classes believed to help ranking — wasted effort.
Why this feature exists
HTML needed author-defined hooks independent of id uniqueness and tag name limits. class from HTML4; multiple classes space-separated. Frameworks (Bootstrap, Tailwind) scaled utility patterns — HTML still stores tokens on elements.
- History: Replaced font and align attributes for separation of concerns.
- Microformats: class h-card — superseded by schema.org JSON-LD mostly.
- Modern: CSS modules hash class in build — HTML still has class, transformed at compile.
Browser internals
DOMTokenList on element.classList supports add/remove/toggle/contains — reflects class attribute string. Matching selectors .foo.bar requires both tokens. getElementsByClassName live collection — avoid during DOM mutation loops.
- Case sensitivity: HTML class ASCII case-sensitive in quirks? Standards mode case-sensitive.
- Invalid chars: Unquoted weird chars break selector matching — stick to [-_a-zA-Z0-9].
- SVG: class on SVG elements — same API in HTML integration.
class="btn btn-primary is-active"element.classList.toggle('is-active', condition)querySelector('.card .title') — descendant match
Rendering workflow
Class changes invalidate style recalc for matching rules — toggling is-loading on body affects many descendants. Specificity from ID+class chains fights cascade — Airbnb uses low-specificity utilities + layers. content-visibility on .below-fold-section class pattern.
- CLS: .hidden { display:none } removed on load — reserve space with skeleton class.
- INP: class toggle per keystroke on 5k nodes — delegate to container class.
- Critical CSS: Above-fold classes inlined — rest async.
Feature deep dive
Conventions: BEM block__element--modifier, or design system prefix ds-button. Separate state classes (is-open, has-error) from structure. Never sole carrier of meaning — pair error class with aria-invalid. data-testid for tests optional if role+name sufficient.
- Multiple classes: Order in attribute irrelevant — specificity and source order matter.
- JS hooks: js-* or data-* prefix — document never style js-* in CSS.
- Utility: Tailwind — HTML verbose, purge removes unused in build.
<button type="submit"class="btn btn--primary"aria-busy="false">Pay now</button><p class="field-msg is-error" id="email-err" role="alert">Invalid email</p>
Accessibility analysis
Classes don't create roles — .button-styled span still span to AT. .visually-hidden must use proven clip pattern, not display:none on important text. State classes should mirror aria-* — is-expanded + aria-expanded=true synced in JS.
- Focus: .focus-visible polyfill pairs with :focus-visible in CSS.
- Color: .text-danger still needs text, not color alone.
- WCAG: Don't hide content from AT with .sr-only misuse on focusable elements.
SEO impact
Search engines ignore class names for ranking — visible text in elements matters. Microformat classes h-entry rare today. JSON-LD preferred. Excessive keyword classes (city-name-spam) signal low-quality templates if paired with thin content.
- Cloaking: .mobile-only different content — must be same for crawlers per Google mobile-first.
- Hidden: .seo-text { font-size:0 } — deceptive hidden text penalty.
- Structured: Class names don't replace schema markup.
Security considerations
User-supplied class names in CMS — injection if reflected in attribute without encoding quotes. Class name " onclick=alert(1) — quote breakout. Sanitize to alphanumeric tokens. CSP limits inline style — class-based styling safer if stylesheet trusted.
- CSS injection: Attacker-controlled class in strict sanitizer — still exfil via attribute selectors if CSS leaked.
- Prototype pollution: Unrelated — but JS reading classList from DOM needs validation.
- Supply chain: Malicious CSS file targeting .admin-panel — scope admin styles.
Performance impact
Tailwind HTML bloat — 50 classes per node increases HTML bytes; build purge essential. Amazon uses component classes — smaller HTML, larger shared CSS cache hit. classList.toggle cheaper than className string concat.
- Selector cost: .wrapper .inner .title deep chains — flatten BEM block.
- @layer: Design system layer order reduces specificity arms race.
- Hydration: Mismatched class SSR/client — flash wrong styles.
Real production example
Shopify Dawn theme uses component classes (card, card__heading) with CSS variables — HTML readable, theme editor maps sections to classes. Stripe Elements minimal classes on container — iframe isolated styles.
- Stylelint: BEM regex on class in HTML templates.
- Critical path: Only LCP component classes in critical CSS.
- Dark mode: .theme-dark on html — one class switch, not per-element.
<article class="product-card"><h2 class="product-card__title">…</h2><span class="product-card__price product-card__price--sale">…</span></article>
Enterprise usage
Enterprise design tokens map to semantic classes (text-body-lg) not px values in HTML. Google mandates prefixed classes g- in some codebases to avoid collision in embeddable widgets.
- Versioning: Major DS bump — codemod class renames.
- CMS: Restrict authors to allowlisted classes in RTE.
- Monitoring: Track CSS bundle vs class proliferation in HTML samples.
Common production failures
Rebrand renamed .blue-header globally — broke third-party embed matching old class. A11y regression when .hidden { visibility:hidden } used for keyboard focusable content — still focusable, invisible.
- Specificity war: !important overrides multiplied — 6-month DS refactor.
- Purge bug: Tailwind removed dynamic class — unstyled checkout button.
- Test: E2E relied on .btn-old — UI changed class, false green tests.
Architecture review questions
- Do class names describe purpose/state, not visual appearance only?
- Are interactive elements semantic tags, not classes on div/span alone?
- Do state classes stay synchronized with aria-* attributes?
- Are user-controlled class tokens sanitized against quote injection?
- Is CSS bundle purged/analyzed for classes actually present in HTML?
- Are micro-frontend class prefixes documented to prevent collisions?
Hands-on project
Refactor a page from presentational classes (red-bold) to BEM semantic names; add Stylelint; verify axe still passes with error state classes + aria-invalid.
- Deliverable: Class naming ADR for team.
- Verify: Purged CSS size before/after Tailwind or build.
- Stretch: Codemod rename across 20 templates.
Interview questions
class vs id for styling and scripting?(Advanced)
class for reusable patterns — multiple elements share. id unique per document — fragment targets, label for, ARIA ids. Don't style with id — specificity traps. JS: prefer data attributes for behavior hooks if class shared with CSS.
Follow-up: Multiple same id bug impact?
Can classes replace semantic HTML for accessibility?(Advanced)
No. class=button on div doesn't give button role, keyboard, or disabled semantics. Classes style and hook JS; tags and ARIA carry meaning. Sync is-open class with aria-expanded only as enhancement.
Follow-up: role=button with class btn?
Tailwind utility classes in HTML — tradeoffs at scale?(Advanced)
Fast authoring, consistent spacing, purge required, verbose HTML hurts TTFB on huge pages, readability suffers in templates. Good with component extraction in frameworks. Monitor HTML weight and purge safelist discipline.
Follow-up: Critical CSS with utilities?
Try it yourself
Edit the HTML, CSS, or JS panels — the preview updates as you type.
Try it yourself
Summary
The class attribute groups styling and scripting hooks across elements without semantic weight. Production systems at Shopify and Google enforce naming conventions, purge unused CSS, and keep accessibility in elements and ARIA — not in class names alone.