スタイリング

checkboxの作り方

スタイリング

基本的なcheckboxの作り方について、一例をまとめます。

ユーザビリティを意識したチェックボックスの場合、デフォルトを消して自身で作ります。

動作の確認

こちらから確認できます。

Checkboxコンポーネント

まずは単体のcheckboxのコンポーネントを作ります。

TypeScriptとスタイルはSass(SCSS)を使用。

import React from 'react';
import { MdCheck } from 'react-icons/md';

export type Props = {
  id: string,
  name: string,
  value: string,
  label: string;
  defaultChecked?: boolean,
  disabled?: boolean,
  onChange?: (event:React.ChangeEvent<HTMLInputElement>) => void,
}

export const Checkbox: React.FC<Props> = ({
  label,
  defaultChecked,
  disabled = false,
  ...props
}) => {
  return (
    <div className="a-checkbox">
      <label className="a-checkbox__label">
        <input
          className="a-checkbox__input"
          type="checkbox"
          defaultChecked={defaultChecked}
          disabled={disabled}
          {...props}
        />
        <span className="a-checkbox__frame">
          <MdCheck className="a-checkbox__icon" />
        </span>
        <span className="a-checkbox__text">{label}</span>
      </label>
    </div>
  );
};

export default Checkbox;

スタイリング

.a-checkbox {
  &__label {
    display: flex;
    align-items: center;
    cursor: pointer;
  }

  &__input {
    width: 0;
    margin: 0;
    opacity: 0;

    &:checked {
      + .a-checkbox__frame {
        background-color: skyblue;
        border: none;

        .a-checkbox__icon {
          font-size: 20px;
          position: absolute;
          top: calc(50% - 10px);
          left: calc(50% - 10px);
          display: block;
          color: white;
        }
      }
    }
  }

  &__frame {
    position: relative;
    box-sizing: border-box;
    display: block;
    width: 22px;
    height: 22px;
    background: white;
    border: 1px solid #c9c9c9;
    border-radius: 2px;
    transition: background-color 0.1s linear;
  }

  &__icon {
    display: none;
  }

  &__text {
    font-size: 16px;
    font-weight: bold;
    display: block;
    line-height: 1.5em;
    margin-left: 16px;
  }
}

解説

スタイリングと関係ない部分も一応解説していきます。

基本的には汎用的に使うためのコンポーネントなので、情報はpropsで渡します。TypeScriptは型定義が必要です。

export type Props = {
  id: string,
  name: string,
  value: string,
  label: string;
  defaultChecked?: boolean,
  disabled?: boolean,
  onChange?: (event:React.ChangeEvent<HTMLInputElement>) => void,
}

checkboxの本体は<input type="checkbox"/>で作りますが、デフォルトだとチェック範囲を広げられずデザインも変えられないです。

<input
 className="a-checkbox__input"
 type="checkbox"
 defaultChecked={defaultChecked}
 disabled={disabled}
 {...props}
/>
デフォルトのcheckbox

手作りのcheckboxを入れている部分

<span className="a-checkbox__frame">
  <MdCheck className="a-checkbox__icon" />
</span>
オリジナルのcheckbox

今回のデザインはかなり似ているので、見た目に関してはオリジナルで作る必要性が伝わらない…

ただデフォルトだとクリック範囲がこの四角の枠内しか押せないです。

ラベルのテキストもまとめてチェックできるのが普通ですよね。

ラベルとセットのクリック範囲

その為には、デフォルトの<input type="checkbox"/>を消します。SCSSのこの部分。

  &__input {
    width: 0;
    margin: 0;
    opacity: 0;

※Tabで操作ができなくなってしまうので、display:noneはアクセシビリティの観点から使うべきではないです

✔︎を自身で作ること方法もあるが、今回はマテリアルデザインのチェックマークアイコンを使用。

react-icon

ここではreact-iconsライブラリを追加していますが、メジャーなアイコンを簡単に使えるので便利です。

使いたいアイコンをimportして、コンポーネントのように書くだけです。

import { MdCheck } from 'react-icons/md';
<MdCheck className="a-checkbox__icon" />

あとは、✔︎の枠のボックスを作って、✔︎自体は最初の時点ではチェックを表示しないスタイルにします。

&__frame {
  position: relative;
  box-sizing: border-box;
  display: block;
  width: 22px;
  height: 22px;
  background: white;
  border: 1px solid #c9c9c9;
  border-radius: 2px;
  transition: background-color 0.1s linear;
}

&__icon {
  display: none;
}

checkされている時のスタイルは、cssのcheckedが便利。

&:checked {
  + .a-checkbox__frame {
    background-color: skyblue;
    border: none;

    .a-checkbox__icon {
      font-size: 20px;
      position: absolute;
      top: calc(50% - 10px);
      left: calc(50% - 10px);
      display: block;
      color: white;
    }
  }
}

Panelコンポーネントと組み合わせる

checkboxのコンポーネントを使ってパネルを作ります。

ちなみに、このように小さいコンポーネントを組み合わせたり使用して、少しずつ大きなコンポーネント単位にしていくアーキテクチャは、atomic designといいます。

Atomic Design を分かったつもりになる

今回のパターンでは、CheckboxがLv1原子(atom)で、PanelがLv2分子(molecules)です。

(classNameも分別するため、a-やm-から始まるようにしていますが、決まりはないです)

Panelのコンポーネント

import React, { useState } from 'react';
import Checkbox from './checkbox.tsx';

const Panel: React.FC = () => {

  const [isSelected, setIsSelected] = useState(false);

  const onClickEvent = () => {
    setIsSelected((current) => !current);
  };

  return (
    <div className={`m-panel ${isSelected ? 'isSelected' : ''}`}>
      <div className="m-panel-label">
        <Checkbox
          defaultChecked={isSelected}
          id="dummy"
          name="dummy"
          label="ラベル"
          value=""
          onChange={onClickEvent}
        />
        {isSelected && (
        <div className="m-panel-label__selecting">選択中</div>
        )}
      </div>
      <p>ユーザビリティ大事ですね</p>
    </div>
  );
};

export default Panel;

スタイリング

.m-panel {
  position: relative;
  padding: 20px;
  background-color: white;
  border: 2px solid #c9c9c9;
  border-radius: 4px;
  transition: 0.2s all;
  width: 300px;

  &.isSelected {
    border: 2px solid skyblue;
  }

  &-label {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;

    .a-checkbox__input {
      &::before {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        content: '';
        cursor: pointer;
      }
    }

    &__selecting {
      font-size: 14px;
      color: white;
      font-weight: bold;
      text-align: center;
      padding: 2px 4px;
      background-color: skyblue;
      border-radius: 2px;
    }
  }
}

イメージ

解説

ここでのポイントもチェックできる範囲。外側の大きな枠内全て選択できるようにします。

その為に、親のelementにposition:relativeを設定して、input type="checkbox"(クラス名:.a-checkbox__input)に全範囲に対応する擬似要素を作ります。

.m-panel {
  position: relative; //親に指定
  padding: 20px;
  background-color: white;
  border: 2px solid #c9c9c9;
  border-radius: 4px;
  transition: 0.2s all;
  width: 300px;

  &.isSelected {
    border: 2px solid skyblue;
  }

  &-label {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;

    .a-checkbox__input { 
      &::before { //擬似要素を作成
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        content: '';
        cursor: pointer;
      }
    }

あとは、チェックしている時はカラーを変えたり、「選択中」のテキストを表示する為、useStateを使って動的スタイルにします。

const [isSelected, setIsSelected] = useState(false);

const onClickEvent = () => {
  setIsSelected((current) => !current); //トグルさせる
};

isSelectedの場合、パネルにクラスを追加して枠線を青色に。

<div className={`m-panel ${isSelected ? 'isSelected' : ''}`}>
&.isSelected {
  border: 2px solid skyblue;
}

加えて、「選択中」テキストを表示

{isSelected && (
  <div className="m-panel-label__selecting">選択中</div>
)}

まとめ

クリック範囲を意識するとチェックボックスを擬似要素で自作で作る必要があるので意外と大変です。

でもユーザビリティを考えると必須で、同時にアクセシビリティの考慮も大事ですね。

(とりあえず、一回型を覚えれば今後は楽そう)