Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic CSS class names do not work #80

Open
AleFossati opened this issue Dec 7, 2022 · 12 comments
Open

Dynamic CSS class names do not work #80

AleFossati opened this issue Dec 7, 2022 · 12 comments

Comments

@AleFossati
Copy link

When using the directive class={someVariableWithTheClassName}, it fails because the generated css class will have a hash, but the applied class won't.

  1. This works perfectly:
<script lang="ts">
  export let black: boolean;
  export let white: boolean;
</script>

<button class:black={backgroundColor === 'black'} class:white={backgroundColor === 'white'} />

<style module>
  .white {
    background-color: #fff;
  }
  .black {
    background-color: #000;
  }
</style>

Result:
image

  1. This fails because the class "black" will have the hash, but the svelte app applies the name without the hash.
<script lang="ts">
  export let bgColor: 'white' | 'black';
</script>

<button class={bgColor} />

<style module>
  .white {
    background-color: #fff;
  }
  .black {
    background-color: #000;
  }
</style>

Result:
image

@AleFossati AleFossati changed the title Dynamic CSS classes names do not work Dynamic CSS class names do not work Dec 8, 2022
@micantoine
Copy link
Owner

Hi @AleFossati

Indeed, passing a dynamic variable as a class will not work because the preprocessor won't be able to interpreter and transform the data in something else before it runs. Since it could contain any value, there is no way to know the exact content and apply the rule from localIdentName to that variable.

The only work around I can think of, is to always append a hash to the value of a dynamic variable. The rule from localIdentName will be ignored which will lead to rethink to the parsing of <style> in order to differentiate the class names written for dynamic variables from the rest.

Could adding a specific syntax help? such as :dyn(.classname). I'm still thinking...

Any suggestion?

@micantoine micantoine added enhancement New feature or request and removed enhancement New feature or request labels Dec 26, 2022
@rammyblog
Copy link

@micantoine Any update on this issue?

@AleFossati
Copy link
Author

Hi @AleFossati

Indeed, passing a dynamic variable as a class will not work because the preprocessor won't be able to interpreter and transform the data in something else before it runs. Since it could contain any value, there is no way to know the exact content and apply the rule from localIdentName to that variable.

The only work around I can think of, is to always append a hash to the value of a dynamic variable. The rule from localIdentName will be ignored which will lead to rethink to the parsing of <style> in order to differentiate the class names written for dynamic variables from the rest.

Could adding a specific syntax help? such as :dyn(.classname). I'm still thinking...

Any suggestion?

Ops, I'm sorry. I solved this problem using the first approach, which is not the best solution, but it works fine. Since then, I completely forgot about this issue, my bad. Tomorrow I will try this once more and come back here with my suggestion.

@AleFossati
Copy link
Author

@micantoine your proposal is to build something like this?

<script lang="ts">
  export let bgColor: 'white' | 'black';
</script>

<button class:dyn(bgColor) />

<style module>
  .white {
    background-color: #fff;
  }
  .black {
    background-color: #000;
  }
</style>

If yes, I think it is a good solution. The only problem would be the syntax highlighting on IDE's 🤔

@patricknelson
Copy link

patricknelson commented Apr 27, 2023

Oh no, I think he was suggesting :dyn(.classname) would go somewhere in the <style module> block.

In that case @micantoine is it simply avoiding hashing that particular class name? Otherwise (given how it processes in the markup stage) how can it know how to hash that dynamic variable?

I’m wondering if maybe instead of :dyn() (or even doing anything at all here), a better fix may in fact to be to just use a unique name and skip <style module> for these particular classes @AleFossati (if not staying with the class: prefixed attribute route). I say that since the use cases for this I think go beyond dynamic values (e.g. a user may want to mix global classes, like say a reusable .button class or whatever) in their <style module> block which would lead to bad code smell.

Just my 2 cents! Not worth much since I only just started Svelte and am only investigating using this module. 😊

@micantoine
Copy link
Owner

Oh no, I think he was suggesting :dyn(.classname) would go somewhere in the <style module> block.

That's right, there is no way to know the content of a dynamic variable, so it becomes impossible to transform it completely when applying the rule of localIdentName.

@AleFossati In the following example, .white & .black has to be linked to the bgColor variable. Typescript serves a different purpose and the preprocessor will not rely on it.

<script lang="ts">
  export let bgColor: 'white' | 'black';
</script>

<button class:dyn(bgColor) />

<style module>
  .white {
    background-color: #fff;
  }
  .black {
    background-color: #000;
  }
</style>

So we need to make a connection between the dynamic variable name and the classnames listed in <style>. My first thought was to use a special syntax with the classname :dyn(.white), but actually it will not work since it's not being connected to the variable bgColor.

The content of the variable cannot and should not be changed

a better fix may in fact to be to just use a unique name and skip <style module> for these particular classes.

One thing that can be done with the current preprocessor is to use the provided :local selector which will apply the default svelte scoping to the class.

<script lang="ts">
  export let bgColor: 'white' | 'black';
</script>

<button class={bgColor} />

<style module>
  :local(.white) {
    background-color: #fff;
  }
  :local(.black) {
    background-color: #000;
  }
</style>

generating

<button class="white svelte-1ijxqhs" />

<style>
  .white.svelte-1ijxqhs { background-color: #fff; }
  .black.svelte-1ijxqhs { background-color: #000; }
</style>

However, if the reason of using the preprocessor is to avoid inheriting styling from a class of the same name; we can elaborate a solution keeping in mind:

  • Do no change the variable name and content
  • Link a class name in <style module> with a dynamic variable
  • Differentiate class names for dynamic variable from the rest (in <style module>)
  • Ignore localIdentName rule or force the use of the [local] token (to keep the classname unchanged)

Suggestion

Use of a special selector in <style module> :dyn(VAR_NAME.CLASSNAME)

  • dyn indicates that the selector is targeting a dynamic variable
  • VAR_NAME is the targeted dynamic variable
  • .CLASSNAME is the class which can be contained in the dynamic variable

Example

<script lang="ts">
  export let bgColor: 'white' | 'black';
</script>

<button class={bgColor} />

<style module>
  :dyn(bgColor.white) { background-color: #fff; }
  :dyn(bgColor.black) { background-color: #000; }
</style>

after preprocessing

<script>
  export let bgColor;
</script>

<button class={`${bgColor}-ha5h0sq`} />

<style>
  .white-ha5h0sq { background-color: #fff; }
  .black-ha5h0sq { background-color: #000; }
</style>

generating

<button class="white-ha5h0sq" />
<!-- or -->
<button class="black-ha5h0sq" />

<style>
  .white-ha5h0sq { background-color: #fff; }
  .black-ha5h0sq { background-color: #000; }
</style>

@AleFossati
Copy link
Author

I agree that it should not rely on typescript, that was a bad idea.
That last example seems nice, using :dyn(bgColor.white) should be good! 👍🏻

@MrHBS
Copy link

MrHBS commented Nov 11, 2023

+1 on the last example

@Enes5519
Copy link

Is the issue I opened here related to this issue? Because although it did not produce any hash in Svelte 4, it started to produce hash in Svelte 5 when I used the class directive.

sveltejs/svelte#15188

@micantoine
Copy link
Owner

@Enes5519 If I understand well your issue is that even though your class has been globalize (:global(.red)), your html still has the svelte scoped class in it <h1 class="red svelte-12df4"></h1>.

This current issue has nothing to do with that and I believe it's not related to the preprocessor.

To be 100% sure, remove the module attribute to <style> and manually globalize all your classes to check if the issue is still happening. Also, try to provide further information and example on the the following issue sveltejs/svelte#15188. It's very hard to replicate your problem without seeing how the component looks.

@Enes5519
Copy link

Thank you for your comment on the issue. I could only produce an example with the preprocessor. The link is here: sveltejs/svelte#15188 (comment)

@micantoine
Copy link
Owner

@Enes5519 it's definitely not coming from the preprocessor. On your example you can disable it and the issue will still persist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants