Create tab pills that morph with pure CSS.

This is what I came up with. It works by using radio buttons and the :last-child selector:


Demo:


First create some basic html:

<div class="tabs">
  <input type="radio" name="tabs" id="a" checked />
  <label for="a">Tab A</label>

  <input type="radio" name="tabs" id="b" />
  <label for="b">Tab B</label>

  <input type="radio" name="tabs" id="c" />
  <label for="c">Tab C</label>
</div>

Then I added following styling:

:root {
  --pill-width: 100px;
  --pill-margin: 20px;
}

.tabs {
  display: flex;
  position: relative;
}

.tabs input {
  display: none;
}

.tabs :last-child::after {
  content: '';
  display: none;
  position: absolute;
  background: rgba(137, 73, 209, 0.2);
  top: 0;
  bottom: 0;
  left: 0;
  z-index: -1;
  border-radius: var(--pill-width);
  transition: left 0.5s;
  width: var(--pill-width);
}

.tabs label {
  display: block;
  width: var(--pill-width);
  text-align: center;
  border-radius: var(--pill-width);
  transition: background-color 0.5s, color 0.5s;
  box-sizing: border-box;
  padding: 5px 10px;
  margin-right: var(--pill-margin);
  cursor: pointer;
}

.tabs label:hover {
  background-color: rgba(209, 209, 209, 0.05);
}

.tabs input:checked + label:hover {
  background: transparent;
}

.tabs input:checked + label {
  color: rgba(148, 84, 221, 0.6);
}

.tabs input:checked ~ :last-child::after {
  display: block;
}

.tabs :nth-child(1):checked ~ :last-child::after {
  left: 0;
}

.tabs :nth-child(3):checked ~ :last-child::after {
  left: calc(var(--pill-width) + var(--pill-margin));
}

.tabs :nth-child(5):checked ~ :last-child::after {
  left: calc((var(--pill-width) + var(--pill-margin)) * 2);
}