This tutorial aims to show you how to build a shiny app like the one below, the dark mode of which is synchronized with the Quarto page.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
## file: app.py
from shiny import *
from pathlib import Path
app_ui = ui.page_fluid(
"This is a Shinylive app with dark theme synced with Quarto", # other ui inputs that you like
ui.head_content(ui.include_js(Path(__file__).parent / "light-dark.js")),
)
app = App(app_ui, None)
## file: light-dark.js
window.parent.addEventListener("quarto-color-mode", function(event) {
document.documentElement.dataset.bsTheme = event.detail.mode;
if (event.detail.mode === "dark") {
document.documentElement.style.setProperty('--bs-body-bg', "#16242f"); // custom dark background color
} else if (event.detail.mode === "light") {
document.documentElement.style.setProperty('--bs-body-bg', "#f9fffe"); // custom light background color
}
})
window.parent.postMessage('ShinyColorQuery', '*'); // request Quarto theme color when Shinyapp is loaded
First, in the Quarto Website Page, we need to add a JavaScript (script?) to communicate with the ShinyLive app, which is in iframe. The adding is done by:
include-after-body:
text: |
<script type="application/javascript" src="js/light-dark.js"></script>
The actual light-dark.js file is inspired by @mcanouil ’s blog post and needs to contain:
const DarkEvent = new CustomEvent("quarto-color-mode", { detail: { mode: "dark" }}); // add new events
const LightEvent = new CustomEvent("quarto-color-mode", { detail: { mode: "light" }});
function updateAppTheme() { // dispatch events when theme needs updating
var bodyClass = window.document.body.classList;
if (bodyClass.contains('quarto-light')) {
window.dispatchEvent(LightEvent);
else if (bodyClass.contains('quarto-dark')) {
} window.dispatchEvent(DarkEvent);
}
}
var observer = new MutationObserver(function(mutations) { // listen for theme changes
.forEach(function(mutation) {
mutationsif (mutation.type === 'attributes' && mutation.attributeName === 'class') {
updateAppTheme();
};
});
})
.observe(window.document.body, { // enable observer
observerattributes: true
;
})
window.onmessage = function(e) { // update theme when receives querry
if (e.data == 'ShinyColorQuery') {
updateAppTheme();
}; }
Back to the ShinyLive app, it also needs to add a JS script to communicate with the parent page. A minimal working ShinyLive app code looks like this:
#| standalone: true
#| components: [editor, viewer]
## file: app.py
from shiny import *
from pathlib import Path
= ui.page_fluid(
app_ui "This is a Shinylive app with dark theme synced with Quarto", # other ui inputs that you like
__file__).parent / "light-dark.js")),
ui.head_content(ui.include_js(Path(
)
= App(app_ui, None)
app
## file: light-dark.js
"quarto-color-mode", function(event) {
window.parent.addEventListener(= event.detail.mode;
document.documentElement.dataset.bsTheme if (event.detail.mode === "dark") {
'--bs-body-bg', "#16242f"); // custom dark background color
document.documentElement.style.setProperty(else if (event.detail.mode === "light") {
} '--bs-body-bg', "#f9fffe"); // custom light background color
document.documentElement.style.setProperty(
}
})'ShinyColorQuery', '*'); // request Quarto theme color when Shinyapp is loaded window.parent.postMessage(
Reuse
Citation
BibTeX citation:
@online{yang2024,
author = {Yang, Dianyi},
title = {How to Sync {Shinylive} Dark Mode with {Quarto}},
date = {2024-09-27},
url = {https://rubuky.com/blog/2024-09-27-AutoShinyLiveTheme/},
langid = {en}
}
For attribution, please cite this work as:
Yang, D. (2024, September 27). How to sync Shinylive dark mode with
Quarto. https://rubuky.com/blog/2024-09-27-AutoShinyLiveTheme/