Every id, class, and data attribute name is implicitly relied upon by other code. Consistent naming conventions turn that implicit reliance into an explicit, predictable, collision-resistant system.
1id For Uniqueness, class For Reuse
id carries a strict contract: exactly one element per document may use a given id value. This makes it the correct tool for document.getElementById() targeting and fragment-identifier anchor links (#pricing), but the wrong tool for styling hooks meant to apply to multiple elements.
class is designed for exactly that reuse case โ the same class can and should apply to many elements sharing a visual or behavioral pattern. Conflating the two (using id for a repeated styling pattern, or accidentally duplicating an id across elements) breaks the assumptions both browsers and JavaScript APIs make about them.
2BEM: Encoding Structure Into Names
As component libraries grow, flat, generic class names (.title, .button, .active) collide constantly across unrelated components. BEM โ Block, Element, Modifier โ solves this by encoding the component hierarchy directly into the class name: block__element--modifier.
card__title unambiguously means 'the title element inside the card block', impossible to confuse with an unrelated .header__title elsewhere in the codebase, even without any additional CSS scoping mechanism. This predictability is exactly why BEM (or similar structured naming systems) remains widely adopted even in codebases using component-scoped styling tools, as a shared vocabulary across the team.
3data-* Attributes For Behavioral Hooks
A subtle but important anti-pattern: using a class both for CSS styling and as a JavaScript querySelector target. When these two concerns share the same name, a well-intentioned CSS refactor (renaming a class for clarity) silently breaks unrelated JavaScript functionality that happened to depend on the old name.
data-* attributes provide a dedicated namespace specifically for this purpose โ data-action="toggle", data-testid="submit-button" โ completely decoupled from the CSS class namespace, so styling and behavior can evolve independently without any risk of one silently breaking the other.
4Step-by-Step Breakdown
Names Are The Interface Between HTML, CSS, And JS. Every id, class, and data-attribute name is a small contract other code depends on โ CSS selectors, JavaScript queries, and other developers all read meaning into these names. A consistent naming convention turns that contract from implicit guesswork into something predictable and searchable.
class Is For Styling Hooks, id Is For Unique References. A common source of confusion is treating class and id interchangeably. id must be unique per document and is the right tool for JS targeting or fragment-link anchors; class is designed for reusable styling hooks applied to multiple elements.
id vs class Purpose. Why is applying the same id to multiple elements on a page a real problem, not just a style violation?
- โIt's purely cosmetic with no functional consequence
- โIt breaks the uniqueness contract JS and CSS ID-selectors and getElementById rely on
- โIt only makes CSS parsing marginally slower
BEM Prevents Class Name Collisions At Scale. BEM (Block__Element--Modifier) is a naming methodology that encodes a component's structure directly into its class names, making relationships explicit and drastically reducing accidental collisions and specificity conflicts as a codebase grows.
BEM Naming. In BEM notation, what does 'card__button--disabled' communicate about the relationship between these three parts?
- โThree unrelated class names applied together
- โ'card' is the block, 'button' is a child element of it, 'disabled' is a state modifier
- โA pure CSS specificity boosting trick with no semantic meaning
data- Attributes For JS Hooks, Not Styling. Using a class as a JavaScript selector hook (document.querySelector('.js-toggle')) tightly couples styling and behavior โ renaming a class for a CSS refactor can silently break JS. data-* attributes provide a dedicated, style-independent namespace for behavioral hooks.
**data-* Attribute Purpose.** Why is using a dedicated data-* attribute for JavaScript targeting preferable to reusing a CSS class for the same purpose?
- โdata-* attributes are queried measurably faster by JavaScript
- โIt decouples styling changes from behavioral hooks, preventing CSS refactors from silently breaking JS
- โThere's no real difference; it's purely stylistic preference
Naming Discipline Established. You now understand the distinct purposes of id and class, how BEM prevents naming collisions at scale by encoding structure explicitly, and why data-* attributes decouple JavaScript hooks from styling concerns.
Follow A BEM Class Naming Convention. BEM names a block's element with a double underscore: block__element.
Level Up ๐
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Naming Conventions Don't Directly Affect Accessibility, But Support It Indirectly
Predictable, well-organized markup is easier to audit and maintain accessibly over time โ a codebase where structure and naming are chaotic makes accessibility regressions harder to catch during review.
SEO Implications
- 1
Class And id Names Have No Direct SEO Weight, But Structural Consistency Aids Maintainability At Scale
Search engines don't read class or id values as ranking signals, but a codebase with predictable naming is easier to keep semantically correct and consistent across thousands of pages over time.
Best Practices
Never Duplicate An id Value Within The Same Document
It breaks the uniqueness contract that getElementById and CSS ID-selectors are built on, leading to unpredictable behavior where only the first matching element is typically affected.
Use A Structured Naming Methodology Like BEM Consistently Across A Codebase
Ad hoc class naming inevitably collides as a component library grows; a consistent, structured system prevents this categorically rather than requiring constant vigilance.
Frequent Bugs
document.getElementById() returns unexpected results after a component was duplicated on a page.
Two elements share the same id value, violating document-wide uniqueness. Ensure each id appears at most once per page, or switch to a class-based selector if reuse is needed.
A CSS class rename during a refactor silently breaks an unrelated JavaScript feature.
The class was being used as both a styling hook and a JS selector target. Add a dedicated data-* attribute for the JS behavior, decoupled from the CSS class name.
Real-World Examples
Fully Decoupled Naming In A Component
A button component cleanly separating unique identification, reusable styling, and behavioral hooks.
<button
id="checkout-submit"
class="btn btn--primary"
data-action="submit-order"
>
Complete Purchase
</button>