sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
79.81k stars 4.24k forks source link

Change body class via <svelte:body /> #3105

Open PaulMaly opened 5 years ago

PaulMaly commented 5 years ago

It's just an idea, but it'll be very convenient if we'll able to switch classes on body element like this:

<svelte:body class:profile={isProfilePage} />
Conduitry commented 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>.

PaulMaly commented 5 years ago

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.

jorgegorka commented 5 years ago

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

PaulMaly commented 5 years ago

@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.

nsivertsen commented 5 years ago

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?

ramiroaisen commented 5 years ago

vote positive on <svelte:body class:name={confition} is usefull

Chaciej commented 5 years ago

+1 It would be great if we could set it like this, from the special component and with SSR.

CanRau commented 5 years ago

+1 for

<svelte:body class:name={confition}>

as well as

<svelte:html lang={lang}>
hgl commented 5 years ago

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.

hgl commented 5 years ago

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}

3tmaan commented 4 years ago

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>  
techa commented 4 years ago

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>
PaulMaly commented 4 years ago

@Conduitry @Rich-Harris Any comments here? Seems this proposal is very popular.

ghost commented 4 years ago

@Rich-Harris

AliBasicCoder commented 4 years ago

i have the same issue

dalisoft commented 4 years ago

@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

niklasgrewe commented 4 years ago

i also find that dynamic class assignments to the body element are really needed, especially in the sapper environment with SSR

fran1990Web commented 4 years ago

where is the solution? Someone knows?

neurocmd commented 4 years ago

I want this feature alongside . On server side it might look like this:

const { head, html, css, htmlAttrs, bodyAttrs } = App.render({ ... })

Sapper template:

<html %sapper.htmlAttrs%>
<head>
  ...
  %sapper.head%
</head>
<body %sapper.bodyAttrs%>
  ...
</body>
</html>
sharpcodepro commented 4 years ago

+1 This is a a must. Of course this can be solved but why resort to hacks.

anito commented 4 years ago

+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>
handfulofcats commented 3 years ago

+1 This would be nice to have!!

kwiat1990 commented 3 years ago

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.

PaulMaly commented 3 years ago

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.

lemmon commented 2 years ago

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.

francoislg commented 2 years ago

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 :)

gspasov commented 2 years ago

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).

oravecz-jpmc commented 2 years ago

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)

maurictg commented 2 years ago

Are there any updates on this? I would really like this feature among with many others!

RomanistHere commented 2 years ago

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.

bru02 commented 2 years ago

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>
givebk-bot commented 2 years ago

Hey! Looks like someone invited me to this issue.

antl3x commented 2 years ago

@givebk-bot !donate @3tmaan $1

thanks for the beautiful solution! ❀️

givebk-bot commented 2 years ago

🎁 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

dawidmachon commented 2 years ago

I would love to see the option to use:

<svelte:body
    class:curious={buttonShow} />
robots4life commented 2 years ago
<svelte:body class:cursorWait />
mattari97 commented 2 years ago

This is an amazing idea. Class based Dark/Light mode is a pain. You need yet another wrapper inside html>body>div>wrapper>content.

aradalvand commented 2 years ago

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 :)

danawoodman commented 2 years ago

I'll PayPal/Venmo anyone who implements support for <svelte:body class={...} /> $100 USD πŸ˜ŽπŸ’΅

RomanistHere commented 2 years ago

I'll PayPal/Venmo anyone who implements support for <svelte:body class={...} /> $100 USD sunglassesdollar

stakes are high, aren't they

PaulMaly commented 2 years ago

@danawoodman not implementation is a hard thing but to get approve of PR from core-team.

danawoodman commented 2 years ago

@danawoodman not implementation is a hard thing but to get approve of PR from core-team.

The core team can split the prize πŸ€“

JbPasquier commented 2 years ago

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 />
danawoodman commented 2 years ago

Haha. Sadly that only works on the client 😿

lemmon commented 2 years ago

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. πŸ€”

danawoodman commented 2 years ago

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" />
FlippingBinary commented 2 years ago

@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>
CanRau commented 2 years ago

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 πŸ‘€

vegardlarsen commented 2 years ago

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} />
W4G1 commented 2 years ago

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>