Skip to content

Commit d1ce879

Browse files
modular theme-toggle
1 parent c108ed5 commit d1ce879

13 files changed

+73
-349
lines changed

README.md

+24-10
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,18 @@ This boilerplate includes modern web features:
2929
- `npm run start:https` - Start with HTTPS for testing secure features
3030
- `npm run build` - Build for production (optimizes CSS)
3131
- `npm run lint` - Run ESLint for code quality checks
32-
- `npm run test` - Run Lighthouse tests
33-
- `npm run test:e2e` - Run Playwright end-to-end tests
34-
- `npm run test:e2e:ui` - Run Playwright tests with UI for debugging
35-
- `npm run test:e2e:update-snapshots` - Update visual test baselines
32+
- `npm run test` - Run Playwright end-to-end tests
33+
- `npm run test:ui` - Run Playwright tests with UI for debugging
34+
- `npm run test:update-snapshots` - Update visual test baselines
3635
- `npm run analyze` - Analyze the site with Lighthouse
3736

3837
## Web Components
3938

39+
The boilerplate includes modular web components that are encapsulated and easily replaceable:
40+
4041
### Theme Toggle
4142

42-
A theme toggle component that allows users to switch between light, dark, and system themes:
43+
A toggle switch component for controlling light, dark, and system themes:
4344

4445
```html
4546
<theme-toggle></theme-toggle>
@@ -50,28 +51,41 @@ A theme toggle component that allows users to switch between light, dark, and sy
5051
- **Multiple theme modes:** Supports light, dark, and system preference modes
5152
- **Preference persistence:** Remembers user's theme choice across sessions
5253
- **System preference detection:** Automatically follows system theme when in auto mode
53-
- **CSS variable integration:** Uses CSS variables for seamless theme switching
54+
- **Self-contained styles:** All styles are encapsulated within the component
5455
- **Responsive design:** Adapts to different screen sizes
5556

5657
#### JavaScript API:
5758

5859
```javascript
60+
// Import the component class if you want to use it programmatically
61+
import { ThemeToggle } from './components/theme-toggle.js';
62+
63+
// Or use the already registered custom element
64+
const toggle = document.querySelector('theme-toggle');
65+
5966
// Get the current theme
60-
const currentTheme = document.querySelector('theme-toggle').getTheme();
67+
const currentTheme = toggle.getTheme();
6168

6269
// Check if dark mode is active
63-
const isDark = document.querySelector('theme-toggle').isDarkMode();
70+
const isDark = toggle.isDarkMode();
6471

6572
// Set theme programmatically
66-
document.querySelector('theme-toggle').setTheme('dark'); // Options: 'light', 'dark', 'system'
73+
toggle.setTheme('dark'); // Options: 'light', 'dark', 'system'
6774

6875
// Listen for theme changes
69-
document.querySelector('theme-toggle').addEventListener('themeChange', (e) => {
76+
toggle.addEventListener('themeChange', (e) => {
7077
console.log('Theme changed:', e.detail.theme);
7178
console.log('Is dark mode:', e.detail.isDark);
7279
});
7380
```
7481

82+
#### Customizing or Replacing:
83+
84+
The theme toggle component is designed to be easily customizable or replaceable:
85+
86+
1. Modify the existing component at `/components/theme-toggle.js`
87+
2. Or create your own component and update the registration in `/components/index.js`
88+
7589
## Accessibility Features
7690

7791
This boilerplate includes several accessibility enhancements:

package.json

-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"@playwright/test": "latest",
77
"eslint": "latest",
88
"globals": "latest",
9-
"http-server": "latest",
109
"lighthouse": "latest",
1110
"npm-run-all": "latest",
1211
"serve": "latest"
@@ -16,7 +15,6 @@
1615
"kill": "npx kill-port 3000",
1716
"lint": "eslint .",
1817
"start": "npx serve ./public",
19-
"start:https": "npx http-server ./public --ssl --cert ./localhost.crt --key ./localhost.key -o",
2018
"test": "playwright test",
2119
"test:staging": "cross-env TEST_ENV=staging playwright test",
2220
"test:ui": "playwright test --ui",

www/components/index.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Component registry
3+
* Import and register all web components here
4+
*/
5+
import { ThemeToggle } from './theme-toggle.js';
6+
7+
// Register the custom element
8+
customElements.define('theme-toggle', ThemeToggle);
9+
10+
// Export components for programmatic usage
11+
export { ThemeToggle };

www/components/theme-toggle.js

+14-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Theme Toggle Web Component
33
* A toggle switch for dark/light mode with system preference detection
44
*/
5-
class ThemeToggle extends HTMLElement {
5+
export class ThemeToggle extends HTMLElement {
66
// Key for storing preference in localStorage
77
#storageKey = 'theme-preference';
88

@@ -246,16 +246,16 @@ class ThemeToggle extends HTMLElement {
246246
this.shadowRoot.innerHTML = `
247247
<style>
248248
:host {
249-
--toggle-bg: var(--theme-toggle-bg, rgba(0, 0, 0, 0.1));
250-
--toggle-color: var(--theme-toggle-color, inherit);
251-
--toggle-hover: var(--theme-toggle-hover, rgba(0, 0, 0, 0.15));
252-
--toggle-active: var(--theme-toggle-active, var(--cc-primary, #4f46e5));
249+
--toggle-bg: rgba(0, 0, 0, 0.1);
250+
--toggle-color: inherit;
251+
--toggle-hover: rgba(0, 0, 0, 0.15);
252+
--toggle-active: #4f46e5;
253253
}
254254
255255
@media (prefers-color-scheme: dark) {
256256
:host {
257-
--toggle-bg: var(--theme-toggle-dark-bg, rgba(255, 255, 255, 0.1));
258-
--toggle-hover: var(--theme-toggle-dark-hover, rgba(255, 255, 255, 0.15));
257+
--toggle-bg: rgba(255, 255, 255, 0.1);
258+
--toggle-hover: rgba(255, 255, 255, 0.15);
259259
}
260260
}
261261
@@ -299,6 +299,13 @@ class ThemeToggle extends HTMLElement {
299299
display: none;
300300
}
301301
}
302+
303+
/* Respect user's motion preferences */
304+
@media (prefers-reduced-motion: reduce) {
305+
button {
306+
transition: none;
307+
}
308+
}
302309
</style>
303310
304311
<button
@@ -348,6 +355,3 @@ class ThemeToggle extends HTMLElement {
348355
}
349356
}
350357
}
351-
352-
// Define the custom element
353-
customElements.define('theme-toggle', ThemeToggle);

www/index.html

+7-15
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,15 @@
33

44
<head>
55
<meta charset="utf-8" />
6-
<meta name="viewport" content="width=device-width, initial-scale=" 1" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
77
<meta name="description" content="" data-site-description>
88
<meta name="theme-color" content="#000000">
99
<meta name="color-scheme" content="dark light">
10-
<!-- Enable View Transitions API -->
1110
<meta name="view-transition" content="same-origin">
1211

13-
<!-- Security headers -->
14-
<meta http-equiv="Content-Security-Policy"
15-
content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'">
16-
<meta http-equiv="X-Content-Type-Options" content="nosniff">
17-
<meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin">
18-
<meta http-equiv="Permissions-Policy" content="camera=(), microphone=(), geolocation=()">
19-
20-
<title data-site-title>Website</title>
12+
<title>Website</title>
2113
<link rel="manifest" href="/manifest.json">
2214
<link rel="icon" href="/favicon.ico">
23-
<link rel="apple-touch-icon" href="/icon/192.png">
2415

2516
<!-- Stylesheets -->
2617
<link rel="stylesheet" href="/styles/all.css">
@@ -31,13 +22,14 @@
3122
<!-- Scripts -->
3223
<script type="module" async src="/scripts/async.js"></script>
3324
<script type="module" defer src="/scripts/defer.js"></script>
25+
<script type="module" defer src="/scripts/transitions.js"></script>
3426
<script type="module" defer src="/scripts/config.js"></script>
35-
<script type="module" defer src="/components/theme-toggle.js"></script>
27+
<script type="module" defer src="/components/index.js"></script>
3628
</head>
3729

3830
<body>
3931
<header view-transition-name="site-header">
40-
<h1 data-site-title view-transition-name="site-title">Website</h1>
32+
<h1 view-transition-name="site-title">Website</h1>
4133
<nav view-transition-name="site-nav" aria-label="Main navigation">
4234
<ul>
4335
<li><a href="/">Home</a></li>
@@ -48,7 +40,7 @@ <h1 data-site-title view-transition-name="site-title">Website</h1>
4840
</header>
4941

5042
<main view-transition-name="main-content">
51-
<h2 data-site-description view-transition-name="page-title">A modern web boilerplate</h2>
43+
<h2 view-transition-name="page-title">A modern web boilerplate</h2>
5244
<div id="app" view-transition-name="app-container">
5345
<!-- Example image with proper alt text -->
5446
<img src="placeholder.jpg" alt="Placeholder image" width="200" height="200">
@@ -58,7 +50,7 @@ <h2 data-site-description view-transition-name="page-title">A modern web boilerp
5850
</main>
5951

6052
<footer view-transition-name="site-footer">
61-
<small>&copy; <span id="current-year">2024</span></small>
53+
<small>&copy; <span id="current-year">2025</span></small>
6254
</footer>
6355
</body>
6456

www/scripts/config.js

-85
This file was deleted.

www/scripts/defer.js

+6-14
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,13 @@ document.addEventListener('DOMContentLoaded', () => {
55
// Initialize behaviors
66
setupIntersectionObservers();
77
setupBotProtection();
8-
setupThemeToggle();
98
setupMotionPreferences();
10-
});
119

12-
/**
13-
* Set up theme toggle interaction
14-
*/
15-
function setupThemeToggle() {
16-
const themeToggle = document.querySelector('theme-toggle');
17-
if (themeToggle) {
18-
themeToggle.addEventListener('themeChange', (e) => {
19-
console.debug('Theme changed:', e.detail);
20-
});
21-
}
22-
}
10+
// Listen for theme change events
11+
document.addEventListener('themeChange', (e) => {
12+
console.debug('Theme changed:', e.detail);
13+
});
14+
});
2315

2416
/**
2517
* Setup motion preference detection
@@ -33,7 +25,7 @@ function setupMotionPreferences() {
3325
}
3426

3527
// Listen for changes to the prefers-reduced-motion media query
36-
window.matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', event => {
28+
window.matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', (event) => {
3729
document.documentElement.classList.toggle('reduced-motion', event.matches);
3830
console.debug('Motion preference changed:', event.matches ? 'reduced' : 'no-preference');
3931
});

0 commit comments

Comments
 (0)