26
loading...
This website collects cookies to deliver better user experience
App.vue
, copy the :root
and :root.dark-theme
styles and add them to your own project's structure.ThemeButton.vue
into your own component file<theme-button />
component wherever you would like to use it.# Generate a vite-based app in the current directory
npm init @vitejs/app .
# Give the package a name, then install the necessary node modules
npm install && npm run dev
For the sake of simplicity, the following steps will all happen inside the App.js file.
I would encourage you to try and use a separate component though.
App.vue
file with the following:<template>
<div class="container-center">
<div class="card">
<input
@change="toggleTheme"
id="checkbox"
type="checkbox"
class="switch-checkbox"
/>
<label for="checkbox" class="switch-label">
<span>🌙</span>
<span>☀️</span>
<div
class="switch-toggle"
:class="{ 'switch-toggle-checked': userTheme === 'dark-theme' }"
></div>
</label>
<p>Wer sitzt dort so spät, bei Nacht und Wind?</p>
<p>Entwickler Clemens, mit einem Pint.</p>
<p>Man hört ihn seufzen, ziemlich hart -</p>
<p>Sonntag ist's, die Deadline naht</p>
</div>
</div>
</template>
<script>
export default {
mounted() {
const initUserTheme = this.getMediaPreference();
this.setTheme(initUserTheme);
},
data() {
return {
userTheme: "light-theme",
};
},
};
</script>
<style>
html, body {
padding: 0;
margin: 0;
}
/* Define styles for the default root window element */
:root {
--background-color-primary: #ebebeb;
--background-color-secondary: #fafafa;
--accent-color: #cacaca;
--text-primary-color: #222;
--element-size: 4rem;
}
/* Define styles for the root window with dark - mode preference */
:root.dark-theme {
--background-color-primary: #1e1e1e;
--background-color-secondary: #2d2d30;
--accent-color: #3f3f3f;
--text-primary-color: #ddd;
}
p {
color: var(--text-primary-color);
}
.container-center {
background-color: var(--background-color-primary);
height: 100vh;
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
}
.card {
padding: 2rem 4rem;
height: 200px;
width: 300px;
text-align: center;
border: 1px solid var(--accent-color);
border-radius: 4px;
background-color: var(--background-color-secondary);
}
</style>
npm run dev
. You should see this when opening your browser:for
attribute in the label
tag points at the correct input element's id
. In this case, both of them are named checkbox. Doing so will cause a click event that hits the label to be reflected by the checkbox.That means: We can get rid of the checkbox and focus on styling the label.
App.vue
file:.switch-checkbox {
display: none;
}
:root
css we've parsed before.:root {
--background-color-primary: #ebebeb;
--background-color-secondary: #fafafa;
--accent-color: #cacaca;
--text-primary-color: #222;
--element-size: 4rem; /* <- this is the base size of our element */
}
--element-size
css variable and use the calc()
function to compute all other dimensions based on it. Since the width of the label is its biggest measurement, we'll bind its value to our root's variable.In a nutshell: We'll use one css variable to describe the scale of the switch
App.vue
file:.switch-label {
/* for width, use the standard element-size */
width: var(--element-size);
/* for other dimensions, calculate values based on it */
border-radius: var(--element-size);
border: calc(var(--element-size) * 0.025) solid var(--accent-color);
padding: calc(var(--element-size) * 0.1);
font-size: calc(var(--element-size) * 0.3);
height: calc(var(--element-size) * 0.35);
align-items: center;
background: var(--text-primary-color);
cursor: pointer;
display: flex;
position: relative;
transition: background 0.5s ease;
justify-content: space-between;
z-index: 1;
}
App.vue
file:.switch-toggle {
position: absolute;
background-color: var(--background-color-primary);
border-radius: 50%;
top: calc(var(--element-size) * 0.07);
left: calc(var(--element-size) * 0.07);
height: calc(var(--element-size) * 0.4);
width: calc(var(--element-size) * 0.4);
transform: translateX(0);
transition: transform 0.3s ease, background-color 0.5s ease;
}
// In the script - part of App.vue
data() {
return {
userTheme: "light-theme",
};
},
userTheme
.<!-- In the template part of App.vue -->
<label for="checkbox" class="switch-label">
<span>🌙</span>
<span>☀️</span>
<div
class="switch-toggle"
:class="{ 'switch-toggle-checked': userTheme === 'dark-theme' }"
></div>
</label>
.switch-toggle-checked {
transform: translateX(calc(var(--element-size) * 0.6)) !important;
}
.dark-mode
and .light-mode
class to our window root element. Based on that, one of the two root - variable scopes will be enforced. To round things up, we'll also use localStorage to add some persistence.App.vue
file:methods: {
setTheme(theme) {
localStorage.setItem("user-theme", theme);
this.userTheme = theme;
document.documentElement.className = theme;
}
}
setTheme
method form above. Let's add the next method straight away:toggleTheme() {
const activeTheme = localStorage.getItem("user-theme");
if (activeTheme === "light-theme") {
this.setTheme("dark-theme");
} else {
this.setTheme("light-theme");
}
}
(prefers-color-scheme: dark)
css selector. It is available to Javascript's window.matchMedia()
method and returns true, if our user's browser prefers dark themes, or false if not.App.vue
file's methods section. It will be called by the already available mounted()
method when the app loads.getMediaPreference() {
const hasDarkPreference = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
if (hasDarkPreference) {
return "dark-theme";
} else {
return "light-theme";
}
},
calc()
a bit to find the fitting size for your appliance.This post was originally published at https://blog.q-bit.me/dark-mode-toggle-component-with-vue-js/
Thank you for reading. If you enjoyed this article, let's stay in touch on Twitter 🐤 @qbitme