Logo

HTML과 CSS로 토글 스위치 UI 만들기

이번 포스팅에서는 순수하게 HTML과 CSS만을 이용하여 아래와 같이 웹에서 어렵지 않게 볼 수 있는 토글 스위치 UI를 만들어보겠습니다.

토글 스위치란?

먼저 웹에서 토글(toggle) 혹은 토글 스위치(toggle switch)라고도 불리는 스위치(switch) UI 대해서 짚고 넘어가겠습니다.

마치 우리가 집 안에서 조명을 켜고 끌 때 사용하는 벽에 붙어있는 스위치를 상상하면 쉬울 것 같은데요. 웹에서도 이렇게 사용자가 어떤 기능을 켜거나 끌 수 있도록 해야하는 경우가 생기기 마련입니다. 좋은 사례로, 알람(notification)을 받을지 말지를 설정하거나, 웹페이지를 다크(dark) 모드로 보여줄지 말지를 설정하는 것을 들 수 있겠네요.

이러한 경우, HTML에서 기본적으로 제공하는 체크박스를 사용할 수도 있지만, 많은 웹사이트에서 체크박스 대신에 커스텀 디지인한 스위치 UI를 제공하고 있습니다. 왜냐하면 체크박스는 일반적으로 여러 개의 옵션을 선택받을 때 사용되기 때문에 직관성이 떨어진다는 단점이 있기 때문입니다.

스위치 UI는 보통 가로로 긴 알약 형태의 도형 안에 엄지 모양의 동그란 도형이 좌우로 움직이는 형태로 디자인하는 경우가 많습니다. 따라서 본 포스팅에서는 이렇게 전형적인 모양의 스위치를 UI를 함께 만들어보려고 합니다.

HTML 코드

스위치 UI를 구현하는 방법에는 여러가지가 있지만 웹 접근성(accessibility) 측면에서 HTML의 체크박스를 토대로 만드는 것이 권장됩니다. HTML의 체크박스는 기능적인 측면에서 스위치와 매우 유사하기 때문에, 불필요하게 직접 구현해야할 부분을 크게 줄일 수 있기 때문입니다.

<label>
  <input role="switch" type="checkbox" />
  <span>알람</span>
</label>

이렇게 <label> 요소로 레이블 텍스트와 <input> 요소를 함께 감싸버리면 텍스트 부분을 클릭했을 때도 스위치가 작동하기 때문에 더 나은 사용자 경험을 제공할 수 있습니다.

레이블 스타일

우선 인라인(inline) 플렉스박스(flexbox)를 사용하여 레이블 텍스트와 스위치의 수직 방향으로 중앙 정렬을 해주겠습니다.

플렉스박스에 대한 자세한 설명은 관련 포스트를 참고하시기 바랍니다.

추가적으로 클릭이 가능하다는 것을 알려주기 위해서 CSS의 cursor 속성을 사용하여 마우스 커서가 스위치나 레이블 텍스트 위로 올라오면 포인터 모양이 나타나도록 해주면좋겠죠?

label {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
}

기본 스타일 제거

스위치 UI를 만들 때는 HTML의 체크박스가 기본적으로 제공하는 스타일이 전혀 필요가 없습니다. 따라서 체크박스에 appearance: none;을 적용하여 기본 스타일을 깔끔하게 제거하도록 하겠습니다.

[type="checkbox"] {
  appearance: none;
}

이제 체크박스이 화면에서 완전히 사라진 것처럼 보일 텐데요. 사실은 체크박스의 크기가 0px x 0px로 완전히 쪼그라들어버린 것입니다.

알약 스타일

이제부터 본격적으로 체크박스을 마치 스위치처럼 보이도록 디자인해볼까요?

우선 체크되지 않은 기본 상태부터 시작할건데요. 테두리를 튀지않는 회색으로 주겠습니다.

알약 모양을 만들어 내기 위해서 너비를 높이보다 길게 잡아주고, border-radius 속성을 높이와 동일하게 해줍니다.

[type="checkbox"] {
  appearance: none;
  position: relative;  border: max(2px, 0.1em) solid gray;  border-radius: 1.25em;  width: 2.25em;  height: 1.25em;}

참고로 체크박스의 너비와 높이는 레이블 요소에 설정된 폰트 크기에 따라 상대적으로 커지거나 줄어들 수 있도록 em 단위를 사용하였습니다. 그리고 max() 함수를 활용하여 테두리의 두께가 2px보다는 얇아지지 않는 선에서 폰트 크기에 비례해서 늘어나거나 줄어들도록 해주었습니다.

엄지 스타일

이번에는 알약 안에서 좌우로 움직이는 소위, 엄지(thumb)이라고 부르는 부분을 디자인해보겠습니다.

HTML의 <input> 요소 안에는 다른 엘리먼트를 넣는 것은 불가능하므로 CSS의 ::before를 사용하여 pseudo element로 추가를 하겠습니다. 완전한 원을 만들기 위해서 너비를 높이를 동일하게 잡아주고 border-radius 속성을 50%로 설정해줍니다. 그리고 transform 속성을 통해서 원의 크기를 살짝 줄여 테두리로 부터 살짝 떨어뜨리겠습니다.

[type="checkbox"]::before {
  content: "";
  position: absolute;
  left: 0;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  transform: scale(0.8);
  background-color: gray;
  transition: left 250ms linear;
}

여기서 주의할 부분은 [type="checkbox"]에는 position: relative;를 적용해주었고, [type="checkbox"]::before에는 position: absolute;를 적용해준 것인데요. 이렇게 해줌으로써 엄지 부분의 위치를 알약 부분을 기준으로 left 속성을 통해서 지정해주고 있습니다.

CSS의 Absolute Position은 본 포스팅의 범위에서 벗어나며 별도의 포스팅에서 자세히 다루었으니 참고하시기 바랍니다.

켜짐 상태 스타일

스위치가 커지면 알약 속에서 왼쪽에 있던 엄지가 오른쪽으로 이동해야겠죠? left 속성을 증가시켜서 왼쪽으로부터 엄지를 떨어뜨리면 됩니다.

추가로 엄지의 색상과 알약의 테두리색과 배경색에 변화를 주어 알약을 좀 더 도드라져 보이게 해주겠습니다.

[type="checkbox"]:checked::before {
  background-color: white;
  left: 1em;
}

[type="checkbox"]:checked {
  background-color: tomato;
  border-color: tomato;
}

불능 상태 스타일

선택이 불가능한 스위치는 굳이 사용자가 클릭해보지 않아도 바로 알아챌 수 있도록 스타일해주는 것이 바람직하겠죠?

테두리색과 배경색과 회색톤으로 바꿔주고 불투명도를 살짝 낮춰주겠습니다. 마우스 커서를 통해서도 체크가 불가능하다고 표시해주면 좋을 것 같네요.

[type="checkbox"]:disabled {
  border-color: lightgray;
  opacity: 0.7;
  cursor: not-allowed;
}

[type="checkbox"]:disabled:before {
  background-color: lightgray;
}

[type="checkbox"]:disabled + span {
  opacity: 0.7;
  cursor: not-allowed;
}

포커스 상태 스타일

웹 접근성(accessibility) 측면에서 마우스 사용자 뿐만 아니라 키보드 사용자도 고려하는 것이 상당히 중요한데요. 키보드 포커스가 스위치로 오면 포커스 링(ring)이 나타날 수 있도록 하여 키보드 사용자도 큰 어려움 없이 스위치를 켜고 끌 수 있도록 해주겠습니다.

[type="checkbox"]:focus-visible {
  outline-offset: max(2px, 0.1em);
  outline: max(2px, 0.1em) solid tomato;
}

호버 상태 스타일

이번에는 마우스 사용자를 위해서 마우스 커서가 스위치 위에 있을 때 그림자 효과를 살짝 주겠습니다. 불능 상태의 스위치에는 마우서 호버 상태에서 이러한 효과를 주면 오히려 사용자에게 혼란을 줄 수 있으니 주의바랍니다.

[type="checkbox"]:enabled:hover {
  box-shadow: 0 0 0 max(4px, 0.2em) lightgray;
}

애니메이션

마지막으로 약간의 애니메이션 효과까지 첨가해주면 금상첨화겠죠? 😁

[type="checkbox"]::before {
  content: "";
  position: absolute;
  left: 0;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  transform: scale(0.8);
  background-color: gray;
  transition: left 250ms linear;}

전체 코드

본 포스팅에서 작성한 코드는 아래에서 확인하고 직접 실행해보실 수 있습니다.

마치면서

지금까지 HTML과 CSS로 간단한 스위치를 함께 구현해보았는데 어떠셨나요? 생각보다 어렵지 않죠?