Update component state when a choice must keep controls, text, and behavior consistent. Override CSS when changing presentation alone. This guide uses a local tile preview in Safari on Mac to show how to choose and verify both approaches.

The reader task is specific: modify a small webpage you author so that its shape controls stay synchronized while its decorative styling remains easy to edit. Here, component state means a JavaScript object managed by the page’s own update function. No framework or product-specific editing feature is assumed.

Which changes belong in component state?

Start by asking what must agree after the change. If selecting a round tile must update a checkbox, the preview, and a written description, give that choice one owner in state. Then derive all three outputs from it. This is an implementation recommendation: it makes the relationship between a choice and its displayed results explicit.

A CSS custom property such as --tile-size has a different role. It supplies a value to declarations that reference it with var(). Custom properties participate in the cascade and normally inherit; their behavior follows CSS rules even when JavaScript sets them. The CSS Custom Properties specification defines this contract.

Use this decision aid before editing:

Requested change Preferred owner Reason in this example
The reader selects a round or square tile Component state The checkbox, description, and shape must agree
The reader chooses a tile size Component state, rendered into CSS The range control, description, and dimensions share one value
Every tile should use a different decorative color Stylesheet declaration No selected value or text needs updating
The preview needs more surrounding space Stylesheet declaration This changes the layout around the tile
A temporary dimension is needed while inspecting Local CSS override It is a visual experiment to remove afterward

The distinction depends on the task. Color belongs in CSS in this preview because it is fixed decoration. If you later add a color chooser and a description of the selected color, treat that selection as state and render its visual effect. Avoid creating two independently editable values for the same choice.

Native controls also have their own state. For a checkbox, read its current checked property. The HTML checked attribute specifies its default checkedness and does not track subsequent user changes. This distinction is documented in MDN’s checkbox property reference.

Build one self-contained preview on your Mac

Use a plain-text code editor to save the following complete document as tile-preview.html, then open it in Safari. Keep the .html extension. The sample contains its CSS and JavaScript, uses system fonts, and draws a plain geometric tile without external assets.

The stylesheet sits in the document’s head. HTML defines the style element as a way to embed CSS, and distinguishes self-contained internal styles from stylesheets that reference additional resources. Keeping this example self-contained makes its styling inputs explicit. See the HTML Standard’s style and scripting sections.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Tile preview</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      margin: 2rem;
      color: #222;
      background: #fff;
    }
    label { display: block; margin-block: 1rem; }
    .preview { padding: 24px; background: #eee; }
    .tile {
      width: 80px;
      height: 80px;
      border-radius: 0;
      width: var(--tile-size, 80px);
      height: var(--tile-size, 80px);
      border-radius: var(--tile-radius, 0);
      background-color: #547899;
    }
  </style>
</head>
<body>
  <h1>Tile preview</h1>
  <label>
    <input id="round" type="checkbox"> Round tile
  </label>
  <label>
    Tile size
    <input id="size" type="range" min="40" max="160"
           step="1" value="80">
  </label>
  <button id="reset" type="button">Reset choices</button>
  <p id="summary"></p>
  <div class="preview">
    <div id="tile" class="tile" aria-hidden="true"></div>
  </div>
  <script>
    const state = { rounded: false, size: 80 };
    const round = document.getElementById('round');
    const size = document.getElementById('size');
    const summary = document.getElementById('summary');
    const tile = document.getElementById('tile');
    const reset = document.getElementById('reset');

    function render() {
      round.checked = state.rounded;
      size.value = String(state.size);
      tile.style.setProperty('--tile-size', `${state.size}px`);
      tile.style.setProperty('--tile-radius', state.rounded ? '50%' : '0');
      summary.textContent = `Tile A: ${state.rounded ? 'round' : 'square'}, ${state.size}px.`;
    }

    function update(patch) {
      const next = { ...state, ...patch };
      if (typeof next.rounded !== 'boolean' ||
          !Number.isInteger(next.size) ||
          next.size < 40 || next.size > 160) {
        throw new RangeError('Use a boolean and a size from 40 to 160.');
      }
      Object.assign(state, next);
      render();
    }

    round.addEventListener('change', () => {
      update({ rounded: round.checked });
    });
    size.addEventListener('input', () => {
      update({ size: Number(size.value) });
    });
    reset.addEventListener('click', () => {
      update({ rounded: false, size: 80 });
    });
    render();
  </script>
</body>
</html>

The style interface used here is documented: element.style.setProperty() writes an inline declaration, including a custom property. Property names use their CSS spelling, such as --tile-size. See MDN’s setProperty reference.

Try selecting Round tile, moving Tile size to a different value, and choosing Reset choices. The first two actions should update both the shape and description; reset should restore a square at 80 pixels. In this program, assigning a field in state alone does not call render(). Route changes through update() so validation and rendering happen together.

The update and render logic was checked in Node with mock controls and a mock style object for initial values, changed values, invalid sizes, and reset. Those checks passed; they do not establish browser rendering. Use the visible checks above to verify the file in Safari.

When should you override a CSS declaration?

For a lasting decorative change, edit the saved stylesheet. For example, change .tile’s background-color to another CSS color, or adjust .preview’s padding. Neither change requires a new state field in this program because neither represents a selected choice.

For a temporary experiment, enable Safari’s inspector through Safari → Settings… → Advanced → Show features for web developers. Open Develop → Show Web Inspector, or press Option-Command-I. These are the documented Mac steps in WebKit’s inspector guide.

With the sample open, the following complete console snippet temporarily changes the tile’s color, then provides a function that removes that override:

const previewTile = document.getElementById('tile');
if (!previewTile) throw new Error('Open tile-preview.html first.');
previewTile.style.setProperty('background-color', '#8069a6');
function restoreTileColor() {
  previewTile.style.removeProperty('background-color');
}
// Run restoreTileColor() when finished comparing colors.

Removing this inline declaration lets the remaining cascade determine the color again. It does not delete the stylesheet’s color declaration. The removal method is defined in MDN’s removeProperty reference. To retain a chosen color across reloads in this example, edit and save the CSS source.

Keep temporary overrides away from values that the renderer owns. If you manually change --tile-size, the description will still report the state value, and the next render() call will replace your experiment. Change the size through the control or update function when you want the complete component to agree.

Why can an override appear to do nothing?

Diagnose the value’s path before increasing selector specificity. A declaration can be valid yet lose in the cascade, or it can target a custom property that the winning declaration no longer uses.

  1. Check the target. Confirm that you selected the element displaying the shape.
  2. Check the input. In the example, size must be an integer from 40 to 160. Invalid updates throw before changing state.
  3. Check the consumer. The tile’s dimensions use var(--tile-size). A winning direct width declaration can bypass that value for width while height still uses it.
  4. Check precedence. Compare applicable declarations, importance, layers, and specificity.
  5. Check the next interaction. If the change disappears after moving a control, inspect what render() writes.

Normal inline declarations take precedence over normal author stylesheet rules. However, importance, animations, and transitions affect the broader order. A later declaration does not automatically win, and a more specific selector cannot solve every conflict. MDN’s cascade guide documents these conditions.

Also distinguish inline values from resolved values. element.style exposes inline styling, so an empty inline value does not mean the element has no styling. getComputedStyle() reads resolved values after active stylesheets have been applied. See MDN’s style reference and getComputedStyle reference.

Run this complete diagnostic snippet in the sample page’s console:

const inspectedTile = document.getElementById('tile');
if (!inspectedTile) throw new Error('Open tile-preview.html first.');
console.table({
  inlineSizeToken: inspectedTile.style.getPropertyValue('--tile-size'),
  inlineWidth: inspectedTile.style.getPropertyValue('width'),
  resolvedWidth: getComputedStyle(inspectedTile).width,
  resolvedHeight: getComputedStyle(inspectedTile).height
});

Immediately after opening the unmodified sample, the expected values are an inline size token of 80px, an empty inline width, and resolved dimensions of 80px. This illustrates how an inline custom property can supply a stylesheet declaration without creating an inline width declaration.

What browser and resource limits matter?

MDN marks CSS custom properties as widely available across browsers since April 2017. That does not establish support in every older browser. The sample includes ordinary dimension declarations before its var() declarations so the tile has a static appearance when custom properties are unavailable. See MDN’s custom properties reference.

The fallback inside var(--tile-size, 80px) does not provide compatibility with a browser that cannot interpret custom properties. Nor does it validate every supplied token: a defined value such as blue cannot become a valid width merely because a fallback exists. These limits are explained in MDN’s custom properties guide.

If an older target browser lacks custom property support, the supported alternative is to change the renderer to write ordinary inline width, height, and border-radius declarations with setProperty(). Keep the same state validation and control synchronization. This changes the rendering contract, so remove competing renderer writes and verify the target browser rather than assuming identical support.

For this local preview, the practical choice is clear: use state for shape and size selections, render those choices through a small set of CSS inputs, and keep decoration in the stylesheet. After any change, test a control interaction and reset. The preview, controls, and description should continue to express the same selected values.

Sources