Semantic Controls

CSS can make almost anything look like a control. HTML determines what the browser understands it to be.

Compare the rendered controls with their markup. Look for differences in purpose, behavior, grouping, name, and state.

Link or Button?

A

Code

<a class="control" href="/experiences/module-05/schedule">
    View schedule
</a>

Rendered

B

Code

<button class="control" type="button" data-in-page-action>
    View schedule
</button>

Rendered

They can look almost identical. Are they the same kind of control?

Reveal
A link navigates. A button performs an action. Styling does not determine semantics.

What Comes For Free?

A

Code

<button class="control" type="button" data-save-workshop>
    Save workshop
</button>

Rendered

B

Code

<div class="control" onclick="saveWorkshop()">
    Save workshop
</div>

Rendered

Save workshop

They look the same. What does the native button already know how to do?

Reveal
  • receive keyboard focus;
  • activate from the keyboard;
  • expose button semantics;
  • follow expected browser behavior.

Native controls already know how to behave.

Is This Actually a Checkbox?

A

Code

<label class="switch">
    <input type="checkbox">
    <span class="switch-track" aria-hidden="true"></span>
    Email reminders
</label>

Rendered

B

Code

<div class="switch" onclick="toggleFakeSwitch(this)">
    <span class="switch-track" aria-hidden="true"></span>
    Email reminders
</div>

Rendered

Email reminders

Both look like switches. Which one actually has a checked state the browser understands?

Reveal
The native checkbox carries checkbox semantics and meaningful checked or unchecked state. Its appearance can change without throwing away those native semantics.

Are These One Question?

A

Code

<fieldset class="segmented-control">
    <legend>Workshop level</legend>
    <div class="segments">
        <label class="segment">
            <input type="radio" name="level" value="beginner" checked>
            <span>Beginner</span>
        </label>
        <label class="segment">
            <input type="radio" name="level" value="intermediate">
            <span>Intermediate</span>
        </label>
        <label class="segment">
            <input type="radio" name="level" value="advanced">
            <span>Advanced</span>
        </label>
    </div>
</fieldset>

Rendered

Workshop level
B

Code

<div class="segmented-control">
    <span class="group-label">Workshop level</span>
    <div class="segments">
        <button class="segment-button is-selected" type="button">Beginner</button>
        <button class="segment-button" type="button">Intermediate</button>
        <button class="segment-button" type="button">Advanced</button>
    </div>
</div>

Rendered

Workshop level

Visually, both let you choose one option. Does the browser know these choices belong to one question?

Reveal
  • Radio buttons communicate mutually exclusive choices.
  • The shared name establishes one choice set, and fieldset/legend can provide its question.
  • Three ordinary buttons remain three ordinary buttons unless their behavior and semantics are deliberately recreated.

Is This Actually an Input?

A

Code

<label class="field-label" for="email-demo">
    Email address
</label>
<input
    class="text-input"
    id="email-demo"
    type="email"
    autocomplete="email"
    value="mira@example.org"
>

Rendered

B

Code

<div class="field-demo">
    <span class="field-label">Email address</span>
    <div class="text-input fake-input">mira@example.org</div>
</div>

Rendered

Email address
mira@example.org

Both may look like places to enter text. Does that mean they communicate the same thing?

Reveal
Visual resemblance does not make an element a native form control. The email input exposes its control type, value, editable behavior, label, and autocomplete metadata to the browser; the generic element is only a styled display. Browsers and assistive technology do not necessarily announce autocomplete metadata aloud.

What State Is It In?

A

Code

<details class="disclosure">
    <summary>Workshop details</summary>
    <p>Bring your laptop. Materials are provided.</p>
</details>

Rendered

Workshop details

Bring your laptop. Materials are provided.

B

Code

<div class="disclosure">
    <div class="disclosure-trigger" onclick="toggleDetails(this)">Workshop details</div>
    <div class="disclosure-content" hidden>
        Bring your laptop. Materials are provided.
    </div>
</div>

Rendered

Workshop details

Both reveal more content. Which implementation communicates that there is an open/closed state?

Reveal
Showing or hiding content is not the whole interaction. Expanded or collapsed is meaningful state, and native disclosure controls provide much of this behavior automatically.

What Is This Control Called?

A

Code

<button type="button" aria-label="Share workshop" class="icon-button">
    <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
        <circle cx="18" cy="5" r="3"></circle>
        <circle cx="6" cy="12" r="3"></circle>
        <circle cx="18" cy="19" r="3"></circle>
        <path d="m8.7 10.6 6.6-4.2M8.7 13.4l6.6 4.2"></path>
    </svg>
</button>
B

Code

<button type="button" class="icon-button">
    <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
        <circle cx="18" cy="5" r="3"></circle>
        <circle cx="6" cy="12" r="3"></circle>
        <circle cx="18" cy="19" r="3"></circle>
        <path d="m8.7 10.6 6.6-4.2M8.7 13.4l6.6 4.2"></path>
    </svg>
</button>

Rendered

A
B

They look identical. Do they have the same name?

Reveal
An icon can communicate visually, but an icon-only control still needs a useful accessible name. A control can have that name without visible text.

What changed?

Start with the native element that matches the interaction.