이번 포스트는 라라벨로 만든 웹사이트에 다크 모드 기능을 추가해 보겠다.
다크 모드는 어차피 구현할 계획이라면 초반에 작업해 두는 것이 좋다.
처음부터 고려하지 않고 페이지를 늘려가다 보면, 나중에 다크 모드를 적용할 때 손대야 할 곳이 너무 많아져 꽤나 귀찮아질 수 있기 때문이다.
이전 포스트
라라벨, 설정한 횟수만큼 연속 로그인 실패 시 설정한 시간만큼 로그인 제한하기
다크 모드 기능 구현 · 4th-avenue/laravel13-livewire@b83c87b · GitHub
전체적인 코드 수정은 이 깃허브 링크를 통해서 확인할 수 있다.
tailwind.config.js 파일 수정
import defaultTheme from 'tailwindcss/defaultTheme';
import forms from '@tailwindcss/forms';
/** @type {import('tailwindcss').Config} */
export default {
darkMode: 'class', // 이 코드를 추가함
content: [
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
'./storage/framework/views/*.php',
'./resources/views/**/*.blade.php',
],
theme: {
extend: {
fontFamily: {
sans: ['Figtree', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [forms],
};
export default { }; 안에 darkMode: 'class'를 추가했다.
이 설정은 tailwindcss가 다크 모드를 어떻게 적용할지 정의하는 부분이다.
darkMode: 'class' 이 코드는 다크 모드가 클래스 기반으로 적용된다는 의미다.
즉, class="dark"라는 클래스를 HTML 요소에 추가할 때, tailwindcss에서 제공하는 다크 모드 스타일이 활성화된다.
예를 들어, class="dark" 속성이 <html> 태그에 추가되면,
다크 모드로 설정된 스타일이 활성화되어 사이트 전체에 다크 모드 테마가 적용된다.
만약 이 설정을 'media'로 변경하면, 브라우저의 시스템 설정(예: 운영체제의 다크 모드)을 기반으로 tailwind가 다크 모드를 자동으로 적용한다.
하지만 'class'를 사용하면 시스템 설정과는 무관하게 HTML에 class="dark"를 추가하거나 제거함으로써 수동으로 다크 모드를 제어할 수 있다.
뷰 파일에 추가할 코드들
welcome.blade.php와 layouts/app.blade.php와 layouts/guest.blade.php, 총 3개의 뷰 파일에 다음의 코드들을 추가한다.
정확한 위치는 위에 링크한 깃허브 링크를 참고하자.
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" :class="{ 'dark': $store.darkMode.dark }">
<head>
<!-- 생략 -->
<script>
if (localStorage.getItem('darkMode') === 'true') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
</script>
<!-- 생략 -->
</head>
<body>
<!-- 생략 -->
</body>
</html>
먼저 <html> 태그에 :class="{ 'dark': $store.darkMode.dark }"> 속성을 추가했다.
이 속성은 Alpine.js의 $store.darkMode.dark의 값이 true면 <html> 태그에 dark 클래스를 붙이고, false면 떼어내는 실시간 바인딩 장치다.
이 코드는 Alpine.js의 store에서 다크 모드 상태를 가져와 <html> 태그의 클래스를 동적으로 설정한다.
초기화 시점에 app.js 파일에서 Alpine.store('darkMode')에서 dark 값을 가져와 다크 모드 여부를 확인하고, 그 값에 따라 <html> 태그에 dark 클래스를 추가하거나 제거하는 역할을 한다.
<head> 태그 안에 넣은 자바스크립트 코드는 웹사이트가 켜지는 첫 0.001초 동안의 화면 깜빡임(Lighthouse 점수와 UX를 갉아먹는 주범)을 원천 차단하는 방패 역할을 한다.
if (localStorage.getItem('darkMode') === 'true'): 브라우저의 전역 객체인window.localStorage를 열어서 'darkMode'의 값이 'true'인지 확인한다.document.documentElement.classList.add('dark');: (참일 때) 만약 기존에 다크모드를 켰던 유저라면, 최상단<html>태그에 'dark'라는 클래스를 즉시 추가한다.document.documentElement.classList.remove('dark');: (거짓일 때) 라이트모드 유저거나 처음 방문한 사람이라면,<html>태그에서 'dark' 클래스를 제거한다.
resources/js/app.js 파일에 코드 추가
// 1. Alpine.js 전역 스토어 설정
Alpine.store('darkMode', {
dark: localStorage.getItem('darkMode') === 'true',
toggle() {
this.dark = ! this.dark;
localStorage.setItem('darkMode', this.dark);
this.apply();
},
apply() {
if (this.dark) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}
});
// 2. [핵심] Livewire가 wire:navigate로 페이지를 바꿀 때마다 실행될 이벤트
document.addEventListener('livewire:navigated', () => {
// 페이지가 바뀔 때마다 로컬스토리지 값을 읽어서 HTML 클래스를 강제로 동기화한다.
const isDark = localStorage.getItem('darkMode') === 'true';
if (isDark) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
});
텅 비어있던 resources/js/app.js 파일에 코드를 추가했다.
주석으로 간단한 설명을 써놓았다.
자세한 설명은 생략하겠다.
livewire/layout/navigation.blade.php 파일 수정
<div class="mx-2">
<button x-data @click="$store.darkMode.toggle()" class="p-1.5 border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 dark:bg-gray-700 rounded-full">
<template x-if="$store.darkMode.dark">
<!-- 다크 모드일 때 아이콘 -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
</svg>
</template>
<template x-if="!$store.darkMode.dark">
<!-- 라이트 모드일 때 아이콘 -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
</svg>
</template>
</button>
</div>
코드를 추가하는 위치는 역시 깃허브 링크를 참고하자.
x-data: 버튼 요소에서 Alpine.js의 상태를 관리하는 데 사용된다. 특별한 값 없이x-data만 사용하면 빈 데이터 스코프를 생성한다.@click="$store.darkMode.toggle()": 사용자가 버튼을 클릭했을 때, Alpine.js의 darkMode 스토어의toggle()메서드를 호출한다. 이 메서드는 다크 모드를 활성화하거나 비활성화하는 역할을 한다.<template x-if="$store.darkMode.dark">: 다크 모드 상태일 때 특정 아이콘을 렌더링하는 조건부 렌더링이다.
이 코드들에 대해서는 다음 포스트에서 다시 한번 더 자세히 설명할 예정이다.
테일윈드 공식 무료 SVG 아이콘 라이브러리 heroicons 특징과 사용법
아이콘은 heroicons에서 가져왔다.
이제 테스트를 해보자.
웹사이트 상단의 내비게이션 바에 다크 모드 버튼이 생겼다.
클릭하면 이렇게 다크 모드가 된다.
페이지를 이동하거나 로그아웃을 한 후에도 다크 모드가 계속 유지되는 것을 확인했다.
여담이지만, 지금 이 화면은 라라벨 11 버전일 때의 라라벨 웹사이트 첫 화면이다.
그런데 분명 지금의 설치된 버전은 13이다.
라라벨 브리즈를 Blade로 설치하면 첫 화면이 라라벨 13 버전으로 유지가 되지만, Livewire로 설치하면 첫 화면이 라라벨 11 버전일 때의 화면으로 바뀌는 현상이 있다.
이것도 일종의 버그인 것인지, 언젠가 해결을 해줄 것인지 모르겠다.
다음 포스트


0 Comments
댓글 쓰기
🔸 댓글은 블로그 운영자의 승인 후에 블로그에 표시됩니다.
🔸 비로그인 방문자 분께서는 '익명'보다 이름/URL로 댓글을 남겨주시면 감사하겠습니다. (URL은 생략 가능합니다.)
🔸 구글 로그인 방문자는 '알림 사용'에 체크를 하시면, 남겨주신 댓글에 대한 답글 알림을 메일로 받아볼 수 있습니다. 📩