🧑‍🎤

SFCの中でCSS Modulesを書く方法

2023/12/18に公開

基本的な書き方

まずは簡単なボタンを定義します。

App.vue
<script lang="ts" setup>
import BaseButton from '~/components/BaseButton.vue';
</script>
<template>
    <div>
        <BaseButton> 送信 </BaseButton>
    </div>
</template>
BaseButton.vue
<script lang="ts" setup>
// このあとpropsを定義します
</script>
<template>
    <button :class="$style.button">
        <slot />
    </button>
</template>
<style module>
.button {
    padding: 8px 16px;
    border-radius: 9999px;
    line-height: 1;
    font-size: 16px;
    font-weight: bold;
    color: white;
    background-color: orange;
}
</style>

<style module>の中で定義されたクラスは<template>内では$styleを通して参照することができます。ちょっと不思議なコードの見た目になりますが、ここまでは普通にクラス名を書くのとそんなに変わらないです。

propsを追加する

状態でスタイルが変化するコンポーネントにするためにpropsを定義します。
disabledがtrueだったらボタンがグレーアウトするようにします。

App.vue
<script lang="ts" setup>
import BaseButton from '~/components/BaseButton.vue';
</script>
<template>
    <div>
        <!-- disabledを追加 -->
        <BaseButton disabled> 送信 </BaseButton>
    </div>
</template>
BaseButton.vue
<script lang="ts" setup>
defineProps<{
    disabled?: boolean;
}>();
</script>
<template>
    <button
        :class="[
            $style.button,
            {
                [$style.disabled]: disabled,
            },
        ]"
    >
        <slot />
    </button>
</template>
<style module>
.button {
    padding: 8px 16px;
    border-radius: 9999px;
    line-height: 1;
    font-size: 16px;
    font-weight: bold;
    color: white;
    background-color: orange;
}
.disabled {
    background-color: gray;
}
</style>

さらに多くのスタイルに対応させる

disabledはtrueかfalseの二択でスタイルが変化していました。
次は、3つのサイズをpropsから指定できるようにします。このような場合は以下のような実装で対応することが可能です。

App.vue
<script lang="ts" setup>
import BaseButton from '~/components/BaseButton.vue';
</script>
<template>
    <div>
        <BaseButton size="sm"> 送信 </BaseButton>
        <BaseButton size="md"> 送信 </BaseButton>
        <BaseButton size="lg"> 送信 </BaseButton>
    </div>
</template>
BaseSelect.vue
<script lang="ts" setup>
withDefaults(
    defineProps<{
        disabled?: boolean;
        size?: 'sm' | 'md' | 'lg';
    }>(),
    {
        disabled: false,
        size: 'md',
    }
);
</script>
<template>
    <button
        :class="[
            $style.button,
            $style[size],
            {
                [$style.disabled]: disabled,
            },
        ]"
    >
        <slot />
    </button>
</template>
<style module>
.button {
    border-radius: 9999px;
    line-height: 1;
    font-weight: bold;
    color: white;
    background-color: orange;
}
.disabled {
    background-color: gray;
}
.sm {
    padding: 4px 12px;
    font-size: 14px;
}
.md {
    padding: 8px 16px;
    font-size: 16px;
}
.lg {
    padding: 12px 24px;
    font-size: 18px;
}
</style>

または、以下のようにスタイルをコードブロックを分けて記述することで可読性を良くすることも可能です。

<script lang="ts" setup>
withDefaults(
    defineProps<{
        disabled?: boolean;
        size?: 'sm' | 'md' | 'lg';
    }>(),
    {
        disabled: false,
        size: 'md',
    }
);
</script>
<template>
    <button
        :class="[
            $style.button,
            buttonSize[size],
            {
                [$style.disabled]: disabled,
            },
        ]"
    >
        <slot />
    </button>
</template>
<style module>
.button {
    border-radius: 9999px;
    line-height: 1;
    font-weight: bold;
    color: white;
    background-color: orange;
}
.disabled {
    background-color: gray;
}
</style>
<style module="buttonSize">
.sm {
    padding: 4px 12px;
    font-size: 14px;
}
.md {
    padding: 8px 16px;
    font-size: 16px;
}
.lg {
    padding: 12px 24px;
    font-size: 18px;
}
</style>

module属性に値を与えることで<template>内に注入されるクラスオブジェクトの名前をカスタマイズできます。スタイルの定義を複数のコードブロックに分割する場合はこのような選択肢もあります。例では、ボタンのサイズに関するスタイルのコードブロックをbuttonSizeと命名することで羅列されたクラス名に意味を持たせることができます。

まとめ

いかがだったでしょうか。SFCは<script><template><style>で構成されていて、3つの言語を1ファイルに記述してコンポーネントを定義することになります。なので深掘れる要素が多く、まだまだ学習の余地があると感じました。SFCのCSS ModulesはComposition APIのuseCssModule()を利用してアクセスすることで、より複雑なスタイルのパターンを持つコンポーネントを1ファイルで記述することも可能です。内容が多くなるのでこちらはまたの機会に紹介しようと思います。

Discussion