Why selecting by ID is not recommended in CSS?
CSSLint gives a guide to why they make their recommendations:
IDs shouldn't be used in selectors because these rules are too tightly coupled with the HTML and have no possibility of reuse. It's much preferred to use classes in selectors and then apply a class to an element in the page. Additionally, IDs impact your specificity and can lead to specificity wars.
(From Disallow IDs in selectors.)
Basically, if you structure your code to use classes rather than IDs, your code can be more general and reusable, which is generally a good thing. Furthermore, specificity is a hard thing to get your head around, and can cause bugs that are hard to find, so if you omit ID selectors, you're less likely to have conflicting rules resolved in unexpected ways.
First of all:
CSS Lint, like JS lint is an opinion disguised as validation, not an authority.
I've been at this for five years. I've worked on very large retail sites, at interactive agencies, as a solo contractor, and am currently at a SaaS firm.
The idea that IDs are unequivocally bad, IMO is only an idea you could arrive at if your CSS-fu when it comes to dealing with teams wasn't that great to begin with.
Good, flexible CSS, IMO, follows the following general best practices:
Always move from general to specific. Font-family and most-common font-size for general body text for instance, should only ever be declared in one place. As you move up to selectors that more typically involve use of classes, you'll start to override font-size. When it comes time to apply a mostly unused font-family to a very specific subset of HTML, that's when it makes sense to use an ID on a container to hit the elements getting the body text variation.
The more generally applied a property is, the easier it should be to override and you shouldn't ever be worried about overwriting your most general properties by accident. This for instance,
body#some_id div
, is an incredibly bad selector to use for that reason. At the 2nd-ish tier (there are 3...ish) where you're typically dealing more with bundles of commonly re-used elements tied to classed containers, it should be a little harder to overwrite those properties by accident. 'By accident' happens more frequently, IMO, when you've got piles of multiple-class selectors all over the place. In the last tier, you should be dealing not with re-usable elements but overrides for highly specific sections of a document. Using an ID in these circumstances is an ideal practice, not a bad one but IDs are best used sparingly and only for the specific properties of the elements in the item container that the ID actually needs to change.The more specific a selector is, the harder it should be to break it by accident. By specific, I don't just mean IDs on the element. I mean an ID'd container element that sits close to the element you're actually targeting. Intentionally overriding, however, should always involve minimal effort, which is where IDs actually help rather than hinder you (see example below). If you ever find yourself in a position of "running out of IDs, classes, and tags" I recommend violence (at least imaginary violence). But seriously, somebody deserves to be hit.
Avoid lengthy selector lists: you should start to feel like you're 'doing it wrong' any time you regularly use 4+ selectors values for a declaration. I would not count > as a tag/value when tallying since using that wisely is an excellent practice for both maintainability and performance.
Avoid Specificity Arms Races: You know the guy. The "don't mess with my stuff" rookie who decided the best way to cover his butt was use every tag, selector and ID he could starting with the body. Don't hire that guy or spank him until he stops. But even on more forward-looking teams, arms races can still start innocently enough if you don't (sparingly) add more weight to selectors in places where it makes sense to.
The minimalist approach in particular is huge. The more you rely on HTML placement to avoid needing CSS in the first place, the better. The better job you've done at distributing the most general styles globally, the less you have to worry about in more specific contexts. The more pinpoint-targeted and concise highly specific property overrides are, the easier they are to manage in the long haul. And this is where the blanket non-ID policy is stupid IMO.
What's more practical and straightforward to override?
This:
body .main-content .side-bar .sub-nav > ul > li > button
Or this?
#sub-nav button
If you generally work solo or only do small projects to keep up the appearance of doing web dev for a living while making your real money at speaking engagements, the upper one might seem silly and unlikely to you but when you work with a team and you're following a class-only policy, and you just got burned for not using high enough specificity recently, it would be very easy to start adding more and more classes and tags to your stuff (just to avoid trouble on a short deadline - you'll change it back later of course), resulting in a gargantuan unpredictable convoluted mess in your CSS files.
So: Try to never use more than one ID and only for properties that are local to a container that doesn't contain other containers. Try to never use more than one or two classes. Avoid classes and IDs that target elements from on-high (the containers they represent are many many ancestor nodes above). More than 3-4 tags is probably too much in most cases until it's time for some maintenance or training/spanking of rookies. And follow the boyscout rule. Zap those importants and obviously overly specific selectors as you go when it's safe to.
But stick with rules of thumb rather than CSS "laws" and make sure you evaluate these ideas in the light of experience. Don't trust anybody or any tool that spouts blanket statements like "never use IDs" for something with as many variables as a CSS layout scenario can have. That's just noob kool-aid as far as I'm concerned and the authors responsible should know better by now if they want to claim to be experts at it.
I disagree with the idea of never using IDs for selecting elements, however I do understand the reasoning.
Oftentimes developers will use a very high specificity selector when the general form will suffice:
#foo #bar #baz .something a {
text-decoration: underline;
}
is probably better written as
.something a {
text-decoration: underline;
}
Additionally, writing styles like:
#foo-1,
#foo-2,
#foo-3,
#foo-4 {
color: #F00;
}
are better written as:
.foo {
color: #F00;
}
Where I differ from CSSLint involves structural IDs that are reused.
I often mark up pages with this structure:
<div id="page">
<div id="page-inner">
<header id="header">
<div id="header-inner"></div>
</header>
<nav id="nav">
<div id="nav-inner"></div>
</nav>
<div id="wrapper">
<div id="wrapper-inner">
<div id="content">
<div id="content-inner">
<article></article>
...
</div>
</div>
<div id="sidebar">
<div id="sidebar-inner">
<div class="module"></div>
...
</div>
</div>
</div>
</div>
<footer id="footer">
<div id="footer-inner"></div>
</footer>
</div>
</div>
And because I know that structure is consistent, I don't mind using #page
, #header
, #nav
, #wrapper
, #content
, #sidebar
, and #footer
in my css for sweeping region-specific styles. These styles are tightly coupled to this particular structure, which makes them less reusable; because I reuse the structure, they are reusable. The important thing to remember is that using an ID in a selector is very specific, and should be used sparingly.
A few words on specificity:
Generally speaking, one should use the lowest possible specificity selector that makes the correct selection:
If you're targeting all <a>
elements, then it makes sense to use a
.
If you're targeting all <a>
elements within <div class="foo">
, then it makes sense to use .foo a
.
If you're targeting all <a>
elements within <div id="bar">
, then it makes sense to use #bar a
. However, you could use a lower specificity selector. [id="bar"] a
has the same specificity as .foo a
, which means that you can still target specific elements by ID without creating selectors with unnecessarily high specificity.
I do not generally recommend using [id="XXXX"]
selectors for selecting on the [id]
attribute, because it's verbose and may cause confusion. I do recommend using [data-id="XXXX"]
selectors for selecting based on custom [data-*]
attributes to more closely relate styles to the current state of DOM nodes.