Open PaulMaly opened 5 years ago
I've considered this before - what should happen with this when compiling for SSR? There's nowhere in the current .render()
response where we could say what classes are on <body>
.
Seems, we already have something similar with head
:
const { head, html, css, body } = App.render({ ... });
And can be used in Sapper like this:
<head>
...
%sapper.head%
</head>
<body %sapper.body%>
</body>
I really believe the body class for pages is a common case for many apps.
In case someone wants to change the class of the body it's very easy.
Add this to the script part of your main layout file:
document.body.classList.add('my-class')
Then in a global css file like public/global.css add the styles for .my-class
@jorgegorka Sorry, but everyone knows that. Here we're talking about a universal (client and server) and declarative way to set body classes. The way you described won't work with SSR and not declarative.
The same kind of thing would be very useful for setting attributes on html
, such as lang
. react-helmet
handles this by returning htmlAttributes
and bodyAttributes
when calling helmet.renderStatic()
[link]. Maybe a similar approach would make sense here?
vote positive on <svelte:body class:name={confition} is usefull
+1 It would be great if we could set it like this, from the special component and with SSR.
+1 for
<svelte:body class:name={confition}>
as well as
<svelte:html lang={lang}>
If you want to change the whole background without having the content being taller than the fold, being able to change body class is pretty vital.
Is there currently a workaround?
If I'm not wrong, using document.body.classList
won't work for SSR.
I want to give body different classes based on which page is active.
BTW, I think this feature might somewhat relate to https://github.com/sveltejs/sapper/issues/374.
That issue is basically asking a way for svelte to render directly at the html/body level, instead of a child node, and that probably affects how components can manipulate {body, html} {classes,attributes}
I don't know what is the status of <svelte:body class:name={condition}
implementation, but in the meantime, I've managed to overpass this issue by using <svelte:head>
instead. As my need was to add styling to my page's body.
<svelte:head>
{#if isModal}
<style>
body {
overflow: hidden;
}
</style>
{/if}
</svelte:head>
Simple theme chooser.
<script>
const themes = ['', 'dark-theme', 'light-theme']
const icons = ['π', 'π', 'β']
let index = 0
</script>
<button on:click={() => { index = (index + 1) % themes.length }}>{icons[index]}</button>
<svelte:body class={themes[index]}/>
<svelte:head>
<style>
/* CSS file */
:root {
--background-color: #a19585;
--text-color: #f7f7f7;
--primal-color: #30a5a7;
}
.light-theme {
--background-color: #fffaf4;
--text-color: #201f20;
}
.dark-theme {
--background-color: #201f20;
--text-color: #fffaf4;
}
body {
background-color: var(--background-color);
color: var(--text-color);
}
</style>
</svelte:head>
@Conduitry @Rich-Harris Any comments here? Seems this proposal is very popular.
@Rich-Harris
i have the same issue
@sveltejs thanks for deleting my comment and i sure you delete this comment. but i should remember to you (who deleted my comment), Svelte isnβt single compiler around world, so keep your @sveltejs yourself. I just tried it and already has experience of deleting my comment
i also find that dynamic class assignments to the body element are really needed, especially in the sapper environment with SSR
where is the solution? Someone knows?
I want this feature alongside
const { head, html, css, htmlAttrs, bodyAttrs } = App.render({ ... })
Sapper template:
<html %sapper.htmlAttrs%>
<head>
...
%sapper.head%
</head>
<body %sapper.bodyAttrs%>
...
</body>
</html>
+1 This is a a must. Of course this can be solved but why resort to hacks.
+1 In the meantime in: _layout.svelte
<script>
import { stores } from '@sapper/app';
import { onMount } from 'svelte';
const { session } = stores();
let root;
onMount( () => {
root = document.documentElement;
}
$: root && (hasUser => root.classList.toggle('loggedin', hasUser))(!!$session.user);
</script>
+1 This would be nice to have!!
Well I think it would be very helpful to be able to change a class or any other attribute on body simply by using <svetle:body>
. I was a little bit surprised that it isn't currently supported.
Yea, this feature would be very very useful for many svelte devs! There's a solution in user-land (for example https://github.com/ghostdevv/svelte-body), but all they're not playing well with SSR. And it's very sad to me.
Sometimes you don't mount the app directly to the body but rather some other element. Perhaps it would be nice if you could target this as well.
All those document.body.classList.add
didn't quite work for me, it was getting triggered too late in the render lifecycle.
I think I found a decent workaround for that:
<svelte:head>
{#if isDarkTheme}
<style>
body {
background-color: #121217;
}
</style>
{:else}
<style>
body {
background-color: #ececec;
}
</style>
{/if}
</svelte:head>
Since this is getting rendered with SSR, the background is pretty much applied as soon as the page renders, which should prevent flickering :)
Another way you could do dynamic changes on body styles from anywhere would be to do something like so:
<script lang="ts">
function Color(hex, selected = false) {
return {hex, selected}
}
const colors = [
Color("#ff0000", true),
Color("#00ff00"),
Color("#0000ff"),
]
$: backgroundColor = colors.find((c) => c.selected)
$: bodyStyle = `
<style>
body {
background-color: ${backgroundColor.hex}
}
</style>
`;
</script>
<svelte:head>
{@html bodyStyle}
</svelte:head>
For me the use case was that I wanted to dynamically change the background color of the <body>
depending on something stored in my store.ts
. This way you can have more control over what the style is and it could be move out is a function even. Not cluttering the html space, or doing multiple if/else statements with multiple <style>
tags inside (which doesn't scale).
Simple theme chooser.
const themes = ['', 'dark-theme', 'light-theme'] const icons = ['π', 'π', 'β'] let index = 0 </script> <button on:click={() => { index = (index + 1) % themes.length }}>{icons[index]}</button> <svelte:body class={themes[index]}/>
@techa Is this aspirational? AFAICT, <svelte:body>
does not support dynamic classes like that. (Although it would be cool)
Are there any updates on this? I would really like this feature among with many others!
I just needed styles on a single page, then removed when the page unmounts, so I did:
<svelte:head>
<style>
html,
body,
#svelte {
height: 100%;
overflow: hidden;
}
</style>
</svelte:head>
Now when I go to another page, it's not overflowed. Both :global()
and a separate css file persisted and prevented me from scrolling on the other pages.
Here is my workaround using dynamic css variables:
<svelte:head>
<svelte:element this="style">
:root {'{'}
--surface-1: {colorScheme.lightvibrant.bg};
--text-1: {colorScheme.lightvibrant.title};
--text-2: {colorScheme.lightvibrant.body};
{'}'}
</svelte:element>
</svelte:head>
Hey! Looks like someone invited me to this issue.
@givebk-bot !donate @3tmaan $1
thanks for the beautiful solution! β€οΈ
π Hey @3tmaan, you have just received U$ 1 from @nthypes!
@nthypes thanks for your support! β€οΈ
@3tmaan, you can check your balance at https://givebk.io.
βββ
(powered by https://givebk.io) ID: d1af9963-7ed6-4399-91ee-ba19f3d20224
I would love to see the option to use:
<svelte:body
class:curious={buttonShow} />
<svelte:body class:cursorWait />
This is an amazing idea. Class based Dark/Light mode is a pain. You need yet another wrapper inside html>body>div>wrapper>content.
I just tried to use the new style directive feature to add a CSS variable to the body element and was surprised to find out that it doesn't work, eventually found this issue:
<svelte:body style:--something={whatever} />
There's lots and lots of use cases for this. This issue has been the most upvoted open issue in the repo for a long time, I think. Deserves a little more love :)
I'll PayPal/Venmo anyone who implements support for <svelte:body class={...} />
$100 USD ππ΅
I'll PayPal/Venmo anyone who implements support for
<svelte:body class={...} />
$100 USD sunglassesdollar
stakes are high, aren't they
@danawoodman not implementation is a hard thing but to get approve of PR from core-team.
@danawoodman not implementation is a hard thing but to get approve of PR from core-team.
The core team can split the prize π€
I'll PayPal/Venmo anyone who implements support for
<svelte:body class={...} />
$100 USD ππ΅
@danawoodman For 2 cents, I have this workaround:
<script>
const isDarkMode = true;
function setDarkModeToBody(node) {
if(isDarkMode) {
node.classList.add('dark');
}
}
</script>
<svelte:body use:setDarkModeToBody />
Haha. Sadly that only works on the client πΏ
Does it necessarily applied to <body>
? Why not just wrap the whole thing in a <div class={whatever}>
and apply your classes there. Make the div min-height: 100vh
. π€
That's possible but also certain tooling expect classes on body. In fact, I'd love for the same support for the html element for the same reason:
<svelte:html class="dark" />
@3tmaan That worked perfectly for me! Thank you! I needed some kind of immediate feedback that a navigation was taking place so the user knows what's happening. It's a little unsettling when you click and nothing seems to happen because a load hasn't been returned by the server yet. With your technique, I simply set the wait cursor while the navigating store is not null. The same technique could be used to easily set dark/light mode or anything else.
<svelte:head>
{#if $navigating !== null}
<style>
* {
cursor: wait;
}
</style>
{/if}
</svelte:head>
Surprised that this still isn't working π₯Ί after I almost exactly 2 years later commented here π₯²
+1 for
<svelte:body class:name={confition}>
as well as
<svelte:html lang={lang}>
who the mango knows what confition
is supposed to be though π
Here is my tiny action that allows you to toggle a class on body (or really any HTML element):
export default function bodyClass(className: string)
{
return function (node: HTMLElement, toggled: boolean) {
node.classList.toggle(className, toggled);
return {
update(toggled: boolean) {
node.classList.toggle(className, toggled);
},
destroy() {
node.classList.remove(className);
}
}
}
}
Usage is simple, but a bit more involved than class:foo
:
<script>
import bodyClass from '$lib/actions/bodyclass.ts';
const classFoo = bodyClass('foo');
const toggle = true;
</script>
<svelte:body use:classFoo={toggle} />
If a theme switcher is what you are looking for, you don't necessarily need to switch classes on body
:
<svelte:head>
{#if isDark}
<style lang="scss">
:root {
--bg-color: #000000;
--text-color: #ffffff;
}
</style>
{:else}
<style lang="scss">
:root {
--bg-color: #ffffff;
--text-color: #000000;
}
</style>
{/if}
</svelte:head>
It's just an idea, but it'll be very convenient if we'll able to switch classes on body element like this: