Set page text defaults on body, let inherited properties flow into components, and target layout rules deliberately. This guide shows how to build and check a local HTML page on a Mac without making every element undo a universal rule.

Why does a universal rule interfere with component defaults?

The universal selector * matches every element. A declaration inside that rule therefore applies directly to each matching element; it does not merely provide a value for descendants to inherit. It also does not directly match pseudo-elements, which require selectors such as *::before. Universal selectors.

Consider this CSS fragment for a component containing a paragraph and a nested text span:

/* Illustrative fragment: assumes a .tile with text descendants. */
* {
  color: #303840;
}

.tile {
  color: #245d75;
}

The tile itself receives the blue text color. Its paragraph and span receive the universal rule’s dark color directly. The component color cannot flow through those declarations by ordinary inheritance. Under the CSS defaulting rules, inherited properties obtain their parent’s computed value when the cascade supplies no value for that property on the element. CSS Cascading and Inheritance Level 4, Defaulting.

This can be confusing because * has zero specificity. Specificity compares competing declarations on the same element within the applicable cascade origin and layer. A declaration on a parent is not a competing selector match on its child. Making the parent selector more specific therefore does not solve this inheritance problem. Specificity.

The useful change is to remove the universal text declaration and establish the default on body. Descendants can then inherit it until a component or element supplies another value. MDN identifies color as an inherited property and explains that inheritance follows the parent’s computed value. Inheritance.

Which defaults belong on the page, a component, or every element?

Before writing a broad selector, decide whether the property should flow through a text subtree, describe one box, or establish an intentional rule for all boxes. The following decision aid applies that distinction to a small authored page.

Desired result Recommended placement Reason
Ordinary page text shares a base color and typography body Gives text a common starting point through inheritance
A tile and its ordinary text share a different color .tile Establishes a new inherited value within that component
A single caption has a different color .caption Applies the exception to the element that needs it
One panel has padding, a border, or a background The panel’s class Keeps box decoration attached to its owner
Boxes use a consistent sizing model A deliberate box-sizing rule Establishes an explicit sizing convention
Headings have an easily replaced margin default A targeted heading selector, optionally wrapped in :where() Limits the default to the intended elements

Inheritance is property-specific. Consult the property reference’s Inherited entry instead of assuming that every declaration on body reaches its descendants. For example, borders do not inherit by default. Inheritance.

A universal sizing rule can still be appropriate. With border-box, an element’s declared width includes its padding and border; margins remain outside that width. That is a specific box-model decision, rather than a reason to apply every page default universally. box-sizing.

Build a complete local demonstration on your Mac

Use TextEdit in plain-text mode or an existing code editor. In TextEdit, choose File > New, then Format > Make Plain Text. Paste the complete document below, choose File > Save, and name it page-defaults.html. If prompted, choose Use .html. Apple documents this workflow in its TextEdit HTML guide.

This example uses synthetic labels and CSS-drawn shapes. It is one complete document with an embedded stylesheet, so there are no additional files to prepare. The code has been inspected here; its rendering has not been executed in a Mac browser.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Page defaults demonstration</title>
  <style>
    *, *::before, *::after {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      font-family: system-ui, sans-serif;
      font-size: 1rem;
      line-height: 1.5;
      color: #303840;
      background-color: #f4f5f6;
    }

    main {
      max-width: 44rem;
      margin: 0 auto;
      padding: 2rem 1rem;
    }

    h1 {
      line-height: 1.2;
    }

    .tile {
      margin: 1.5rem 0;
      padding: 1.25rem;
      border: 2px solid currentColor;
      border-radius: 0.75rem;
      color: #245d75;
      background-color: #ffffff;
    }

    .tile h2 {
      margin-top: 0;
    }

    .shape {
      width: 4rem;
      height: 4rem;
      background-color: currentColor;
    }

    .shape--round {
      border-radius: 50%;
    }

    .caption {
      color: #66523c;
    }
  </style>
</head>
<body>
  <main>
    <h1>Shape notes</h1>
    <p>Page sample: <span>Label A</span>.</p>

    <section class="tile">
      <h2>Tile B</h2>
      <div class="shape shape--round" aria-hidden="true"></div>
      <p>Component sample: <span>Label C</span>.</p>
      <p class="caption">Caption D has its own text color.</p>
    </section>

    <p>Page sample: <span>Label E</span>.</p>
  </main>
</body>
</html>

Open page-defaults.html in your browser. When reopening the source in TextEdit, choose TextEdit > Settings > Open and Save and enable Display HTML files as HTML code instead of formatted text if you want TextEdit to show the markup consistently. Work with HTML documents in TextEdit on Mac.

Check the inheritance boundary with three edits

These checks turn the document into a small diagnostic exercise. Save the source and reload the browser after each edit. Keep a copy of the original document or undo each change before trying the next one.

  1. Change only the body color to #4b465f. The text around Label A and Label E should change. The tile retains its own color.
  2. Restore the page color, then change only .tile to color: #526637. Its ordinary heading and paragraph text should follow that change, including Label C. Caption D retains its explicit color.
  3. Restore the tile color, then temporarily add * { color: #303840; } at the start of the stylesheet. The paragraph containing Label C now receives that direct declaration. Remove the temporary rule afterward.

The checks illustrate the distinction between a component establishing an inherited value and a descendant receiving a declaration of its own. The expected text behavior follows the specification’s inheritance and defaulting rules.

What is the contract between the HTML page and its stylesheet?

Keep resource loading separate from selector behavior. The example embeds its CSS in a style element. If you prefer a separate stylesheet, move only the contents of that element into a plain-text file named page-defaults.css, saved beside the HTML file. Replace the entire style element with the following line inside head. This is a replacement fragment, not another complete document.

<link rel="stylesheet" href="page-defaults.css">

The stylesheet link type identifies an external CSS resource used to present the document. The filename in href must point to the file you actually saved; the declaration does not create that file. The HTML Standard defines this relationship in Link type: stylesheet.

For this example, the resulting contract is simple: the HTML provides elements and classes, while the CSS provides their presentation. Renaming .tile in the stylesheet requires updating the corresponding class in the HTML. Moving the CSS file requires updating its resource reference. Neither change calls for a stronger selector.

If the external version appears unstyled, first confirm that the CSS file exists under the expected name and that its contents contain CSS rather than the surrounding HTML document. Return to the embedded version to isolate resource-loading mistakes before changing the cascade.

When should you use :where() or cascade layers?

Use these features when they solve an additional problem. The complete example above does not depend on either one.

Use :where() for targeted defaults with zero specificity

Suppose the page grows to include several heading levels. This optional stylesheet fragment establishes shared spacing while allowing ordinary heading selectors to replace it easily:

/* Optional fragment: add to the example stylesheet. */
:where(h1, h2, h3) {
  margin-bottom: 0.75rem;
}

h2 {
  margin-bottom: 1rem;
}

The entire :where() selector has zero specificity, including its arguments. But replacing * with :where(*) does not fix the earlier color problem: it still matches every element and supplies a direct declaration. Use inheritance to address that problem. MDN documents :where() as broadly available across browsers since January 2021; check its compatibility table when supporting an older browser. The :where() pseudo-class.

For a browser without :where() support, use an ordinary selector list such as h1, h2, h3 for these spacing defaults, then place equal-specificity exceptions later in the same stylesheet and layer. This retains targeted defaults, although their specificity is no longer zero. Specificity.

Use layers when groups of rules need an explicit order

For a larger stylesheet, this optional fragment demonstrates a declared layer order. It is separate from the complete example rather than a replacement for its full CSS.

/* Optional fragment for browsers supporting cascade layers. */
@layer defaults, components;

@layer defaults {
  p {
    margin-bottom: 1rem;
  }
}

@layer components {
  .tile p {
    margin-bottom: 0.5rem;
  }
}

For normal author declarations, later layers take precedence over earlier layers; normal unlayered declarations outrank layered declarations. Important declarations reverse the layer order, so adding !important changes that arrangement. Layers organize competing rules; they do not make a directly styled child inherit its parent’s value. The @layer at-rule.

MDN records cross-browser availability for @layer since March 2022. If an older target browser lacks support, keep essential presentation outside layer blocks and use the unlayered approach shown in the complete example. Do not place the only copy of required styles inside an unsupported feature. Cascade-layer support.

How do you fix an existing stylesheet without adding more overrides?

Work through one property at a time. For a text-color problem, locate the broad rule that assigns the color directly to descendants. Remove that declaration from the universal rule, establish the page value on body, and keep component colors on their component roots. Retain element-level declarations only where the design requires an exception.

Then repeat the three edits from the demonstration. If ordinary component text still fails to follow its parent, look for a declaration targeting that descendant. If changing a component’s border changes none of its descendants’ borders, that is expected: border inheritance differs from text-color inheritance. Inheritance.

A maintainable default has a clear owner. Put page typography at the page boundary, component presentation at the component boundary, and deliberate exceptions on the elements that need them. Reserve universal declarations for decisions you actually intend to apply to every matching element, then verify those boundaries with a small local page.

Sources