Pure CSS tabs with four navigation controls above a content panel
Blog

How to Create Pure CSS Tabs with HTML and CSS

Pure CSS tabs let readers switch between related content panels without JavaScript. This tutorial uses accessible radio inputs and labels, so the controls work with a mouse, touch, and keyboard. Try the live example below, then use the HTML and CSS snippets to build your own tab component.

Quick answer: Give each radio input the same name, connect each visible label with a matching for and id, and use the CSS :checked selector to reveal the corresponding panel.

Try the Live DemoDownload Source ZIP

What are CSS tabs?

Tabs organize several short, related sections in one compact area. In this example, selecting a tab checks a radio input. CSS then highlights its label and displays the matching panel. Because the selection is handled by the browser, no JavaScript is required.

1. Add the tab controls in HTML

Place the radio inputs, labels, and content panels in the same parent. Each radio input needs a unique ID; its label’s for value must match that ID. Keep the inputs in the document so keyboard users can focus and select them.

2. Use CSS to show the selected panel

Hide the panels by default, then use a checked input and the general sibling selector (~) to display the matching panel. The adjacent sibling selector (+) styles the label immediately after the selected input. The complete, formatted examples are below.

3. Make the tabs responsive and keyboard friendly

Use a flexible grid for the tab labels and let it fit the available width. Keep a clear :focus-visible outline, provide readable label text, and avoid removing keyboard focus. The example adapts the spacing on narrow screens and honors reduced-motion preferences.

Try the interactive pure CSS tabs

Select Overview, Features, Styling, or Accessible. The active panel changes using CSS only.

HTML markup

Use the HTML code block below as the component structure. Update the labels and panel copy to suit your project.

CSS styles

The CSS code block below styles the controls, selected state, focus indicator, panel visibility, and small-screen layout.

Common issues

  • A panel does not appear: Check that each selector’s input ID matches the panel selector exactly.
  • A tab label does not select its input: Make sure the label’s for value matches the radio input’s id.
  • Keyboard focus is hard to see: Keep the focus outline and test navigation with Tab and the arrow or Space keys.
  • Use tabs for related content: If readers need to compare every section at once, show the content as ordinary sections instead.

Frequently asked questions

Can tabs work without JavaScript?

Yes. Radio inputs retain the selected state, and CSS selectors show the matching panel. This is suitable for simple, self-contained tabs.

Are CSS-only tabs accessible?

They can be made keyboard operable with native radio inputs and associated labels. Keep visible focus styles, clear names, and a logical reading order. For more complex tab behavior, use a tested accessible component with the expected ARIA and keyboard pattern.

Can I add more tabs?

Yes. Add another radio input and label, then add its panel and a matching checked-state selector. Keep the radio group name consistent and all IDs unique.

Use the demo to explore the selected states, or download the source ZIP to run the example locally.

Interactive example · HTML + CSS

Try the pure CSS tabs

Choose a control to reveal its panel. The interaction uses radio inputs and CSS only.

One component, no script

The selected radio input stores the state. CSS reveals the matching panel and styles its label.

HTML + CSS only

Easy to adapt

Use a compact tab group for a feature list, a product overview, or a short set of related notes.

Four responsive choices

Match your design

Change the accent color, corner radius, spacing, and panel background to fit your own site.

Customizable styles

Keyboard-ready controls

Keep the radio group focusable and use the arrow keys to change the selection. A visible focus ring is included.

No hover required
HTML markup
<fieldset class="swc-tabs-demo__controls" aria-label="Choose a content panel">
  <legend hidden>Choose a content panel</legend>

  <input type="radio" name="swc-tabs-demo" id="swc-tab-home" checked>
  <label for="swc-tab-home">Overview</label>

  <input type="radio" name="swc-tabs-demo" id="swc-tab-features">
  <label for="swc-tab-features">Features</label>

  <input type="radio" name="swc-tabs-demo" id="swc-tab-styling">
  <label for="swc-tab-styling">Styling</label>

  <input type="radio" name="swc-tabs-demo" id="swc-tab-accessibility">
  <label for="swc-tab-accessibility">Accessible</label>

  <div class="swc-tabs-demo__panels">
    <section class="swc-tabs-demo__panel" id="swc-panel-home">
      <h3>One component, no script</h3>
      <p>CSS reveals the panel that matches the selected radio input.</p>
    </section>
    <section class="swc-tabs-demo__panel" id="swc-panel-features">
      <h3>Easy to adapt</h3>
      <p>Replace these examples with your own related content.</p>
    </section>
    <section class="swc-tabs-demo__panel" id="swc-panel-styling">
      <h3>Match your design</h3>
      <p>Change the colors, borders, and spacing in the stylesheet.</p>
    </section>
    <section class="swc-tabs-demo__panel" id="swc-panel-accessibility">
      <h3>Keyboard-ready controls</h3>
      <p>Keep each radio focusable and each label visible.</p>
    </section>
  </div>
</fieldset>
CSS styles
.swc-tabs-demo__controls {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}

.swc-tabs-demo__controls input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.swc-tabs-demo__controls label {
  display: grid;
  min-height: 52px;
  place-items: center;
  border-radius: 12px 12px 0 0;
  background: #f7f9fd;
  color: #53627c;
  cursor: pointer;
}

.swc-tabs-demo__controls input:checked + label {
  background: #315fd1;
  color: #fff;
}

.swc-tabs-demo__controls input:focus-visible + label {
  outline: 3px solid #f6b73c;
  outline-offset: 3px;
}

.swc-tabs-demo__panel {
  display: none;
}

#swc-tab-home:checked ~ .swc-tabs-demo__panels #swc-panel-home,
#swc-tab-features:checked ~ .swc-tabs-demo__panels #swc-panel-features,
#swc-tab-styling:checked ~ .swc-tabs-demo__panels #swc-panel-styling,
#swc-tab-accessibility:checked ~ .swc-tabs-demo__panels #swc-panel-accessibility {
  display: block;
}

@media (max-width: 520px) {
  .swc-tabs-demo__controls {
    gap: 5px;
  }

  .swc-tabs-demo__controls label {
    min-height: 48px;
    font-size: .76rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .swc-tabs-demo * {
    transition-duration: .01ms !important;
  }
}