Detailed explanation
The universal selector, written as *, matches any element within its applicable scope, subject to CSS namespace rules. Used alone in a normal page stylesheet, it matches every element in the document, including html and body, but not text or comment nodes. It is useful when a declaration should apply consistently across elements without listing their tag names or classes. For example, * { margin: 0; } removes default margins from all matched elements, although such a broad reset may require restoring useful spacing later.
A common practical use is *, *::before, *::after { box-sizing: border-box; }. With border-box, an element’s specified width and height include its padding and borders, making layout dimensions easier to reason about. Margins remain outside those dimensions. The extra selectors matter because * alone does not select pseudo-elements: *::before and *::after explicitly target those pseudo-elements for every originating element. This rule styles them if they exist; it does not create them. The universal selector itself contributes zero specificity, so it does not increase a selector’s priority when rules compete.
Its performance implications need careful wording. Modern browsers optimise CSS selector matching, and matching * against an individual element is simple. The concern is its broad reach: a document-wide rule can apply to many elements, increasing the amount of style work involved when styles are initially calculated or later recalculated. However, it is misleading to say that * always makes a page slow or that browsers necessarily scan every DOM node whenever it appears. Actual cost depends on DOM size, the complete selector, style invalidation, and the properties being applied.
In interviews, distinguish selector-matching cost from layout and painting costs. A global box-sizing reset is common and generally reasonable; replacing it solely for assumed speed is rarely justified. Prefer targeted selectors for component-specific styling because they communicate intent and prevent accidental effects. For a genuinely slow page, use browser performance tools to identify expensive style recalculation, layout, or painting before blaming the universal selector.
Key points to remember
- * matches elements, not text nodes, comments, or pseudo-elements directly.
- The universal selector itself adds zero specificity.
- Use *, *::before, *::after to apply border-box sizing to elements and their before/after pseudo-elements.
- Broad matching can increase style work, but * is not inherently a performance problem in modern browsers.
- Use global rules deliberately and measure actual rendering costs before optimising selectors.
Common mistakes candidates make
- Claiming that * selects every DOM node, including text nodes and pseudo-elements.
- Saying the universal selector is always slow or must be avoided, without considering browser optimisations and measurements.
- Using broad resets for component-specific styling and unintentionally changing unrelated elements.
Likely follow-up questions
What is the specificity of * compared with a class selector?
The universal selector has specificity 0-0-0, while a class selector has specificity 0-1-0. For competing declarations at the same cascade priority, the class selector wins.
What does .card * select?
It selects all descendant elements inside an element with class card, at any nesting depth. It does not select the card element itself; .card > * selects only its direct child elements.
How would you check whether a universal selector is causing performance problems?
Record the slow interaction with browser developer tools and inspect style recalculation, layout, and paint activity. Compare a controlled change to the suspected rule, ensuring that any improvement is not simply caused by changing the layout or visual output.
Interview tip: Define what * matches, show the box-sizing reset, and explain that broad applicability—not inherently expensive matching—is the main performance consideration.
