-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Component style exposure mechanism #8538
Comments
I wholeheartedly agree with OP and all the other people that called for this feature. I'm absolutely loving Svelte for my pet projects, and I'm even trying to push it at work. However, I have to say, that the complete lack of (scoped) style delegation feels like a huge oversight. As soon as your application reaches a certain size, where you might want to override a few properties of even something as basic as a I hope we'll finally find a solution for this soon, otherwise Svelte will stay behind React and Vue in adoption at bigger companies. |
The solution is Tailwind. Every other dev I've talked to with a sufficiently complex Svelte app has dropped Svelte's broken |
Tailwind is a great tool especially to achieve fast styling, but in this context it is more a painkiller than a solution. Also it comes with 2 big counterparts that are a "no go" for me in a big project context :
In fact Tailwind is not CSS. It is something different, a whole new syntax with different ways to achieve same goals as CSS. The mechanism I asked for should rely on pure CSS. Plus, exposing the
|
In my opinion the best mechanism for theming/styling components by far is the Web Components |
I still think that the better syntax would be one pertained to the opinionated way of embedding/extending the functionalities of another module/component that the language already has, as I suggested on my comment that was quoted. Having special default slots to bridge aspects others of composition than structure/templating, such as interactions and extensions of existent behavior/presentations, does not only feel the most natural to Svelte, but reincurs concerns and design goals to solutions and mechanisms already present within Svelte: Ownership and control is a concern? Benefits I can see from this syntax
|
Describe the
problemcontextI've been playing with Svelte for nearly a year now and it has been a major game changer for me. I'm absolutely in love with it 😍.
However, I'm still struggling when it comes to style generic components.
Long story short
Couple of month ago, I've started to build a component library for myself. Everything was going great until the question of styling. How do I allow future library users to style each component to benefit from mechanics but adapt them entirely to their application interface.
Styling is a big part of frontend development and so far, despite some funny stereotypes, I find CSS a pretty cool tool to achieve beautiful and intuitive interfaces. However, with svelte and scope-style, I can't use css out of the box to style the component wherever I use them. I have to either use javascript/typescript to expose style purpose variables, expose classes to do in DOM styling or break style scope (or multiply css variables in a specific and tricky way, I'll talk about it further as a workaround).
First, I really wish to keep styles scoped as much as possible. Second, exposing multiple css variables as component props is such a bad idea as it won't work without javascript, induces hard-to-read structure and is just unpleasant. So the only way to achieve components styling is to expose classes in a tailwind like way... Which kind of break style scoping, is unpleasant in my humble opinion and to summarize NOT CSS.
This is an issue that many svelte users are facing, and many ideas were proposed, mainly based on allowing component to be styled from outside. All these proposals were rejected, and rightly so. A list of some proposals and discussions is available below but I think that RFCs @pngwn comment beautifully summarizes maintainers opinion and the problems with the approaches so far proposed (I strongly suggest you to read it).
Long story LONG
I do understand the desire of maintainers to keep svelte opiniated to avoid misuse.
Also, I share the vision that components should remain "their own bosses" meaning self-explainatory and aware of its exposed part. A component variable setting must be explicitly exposed by the component. This way, users can easily understand how the component works and how it should be used.
Therefore I completely agree with the negative response to the last feature requests regarding the opening of components to CSS class injection or similar concepts.
That said, I strongly disagree with the statement "component should not accept styles from outside". Waging war against parent styling children is waging war against theming and more generally genericity. Not having parent able to style components is drastically limiting component reuse which is one of the main advantage of Svelte and similar frameworks. CSS and styling of a component can't be isolated from context.
In the current state svelte does not allow to expose the styles of a component in a comfortable way. HTML structure can be exposed using slot API. JS code can be exposed using props. But CSS styles have no exposure mechanism; understand self-explainatory, explicit and comfortable mechanism.
Of course we can still expose styles by exposing props to use in style attribute. It works but it present major drawbacks :
<style>
tag whereas styling should just be modifiying<style>
tag only.Another way around is to use
:global()
. After reading many comments on the subject, at least everyone seems to agree that this is not a good and practical thing to do : This is equivalent to using global naming and does not take advantage of Svelte scoping. That's a problem everybody seems aware of.Related issues :
<style module>
#6972Related comments :
Describe the proposed
solutionconceptInspired by @tncrazvan idea, multiple comments and my own researches, I here present what I think would be the ideal mechanism to adress those problems. Keep in mind that by "Ideal mechanism" I only suggest that it addresses the previously presented problems while respecting Svelte opiniated philosophy.
The main idea is to allow a component to describe explicitly and precisely through CSS which styles are exposed to parent(s).
In
component.svelte
Note that we could also imagine styles completely exposed with a
<style expose>
tag in similar way of<style global>
.In
parent.svelte
Of course the exposed/overrided styles would benefit from scoping and hash naming. I don't know exactly how to achieve that at compilation but I do understand that it requires additional and potentially complex logic. However I could imagine compilation to give something like below, with overriding based on CSS rules position.
Or we could imagine an even more precise overriding based on the css rules comparison, which would require even more complex compilation logic, but no pain no gain 😁
I would be happy to work on this more deeply when time comes, but I think this is sufficient to understand the concept of my proposal.
This solution would allow CSS exposition through CSS which make a lot of more sense than exposing CSS variables through props.
Describe the Alternative
Description
In this journey of svelte css styling, I gradually developed an alternative solution based on native css variables. Although it works in most use cases, it is quite heavy.
It looks more like a best practice guide.
Whenever I create a component destined to be used in multiple context with different styles, I use css native variables to expose the css property that I consider stylable. For each 'exposed' css property, I define 2 native variable, one to be used outside by parent and one default variable which can store default value (quite handy for theming).
It gives me a pretty simple component that I can style from parent with default styles depending on global template. Paired with exposed class, we can also define multiple default templates.
In
component.svelte
In a template global file
template.css
In
parent.svelte
This technique has numerous advantages :
:global()
in most cases,<style>
tag,These are the main reasons that pushed me to introduce this solution as a pretty neat alternative to a css exposure mechanism. HOWEVER, some limitations remain (remember when I said "an almost complete solution" 😁)
Limitations
This alternative perfectly works when we use components inside native DOM elements but things get tricky when you wan't to use your stylable component inside other stylable component without having to add DOM elements.
Parent context
The first limitation is about parent context. If I use my component inside another and I want to apply a specific style depending on its parent context (focused, hovered or with a special class), I need to anticipate all of that inside the child component :
Say I have an
Icon
and aButton
components.Icon.svelte
Button.svelte
I can change the
Button
color depending on its context : hover or not. But if I wan't to use anIcon
directly inside theButton
, I can't change its stroke color depending onButton
context.anywhere
I actually found a solution which works for 1 level but kind of break the idea of component "independancy" and agnostic of its context :
Icon.svelte
anywhere
Note that it won't work as such if Icon is not a direct children of Button and also that this solution adds a ton of global css rules...
Component variants
Similarly, multiple questions arise when we wan't to use stylable components inside others without adding native DOM element. For example when I wan't to create a new stylable component based on a combination of others such as a
Button
variant with an Icon, several questions remain about how to treat their css variables :IconButton.svelte
Most of the time the answer depends on the usecase and induce tradeoffs.
Also, the idea of default styles works great for 1 level composition but gets harder when using mutliple ones...
This limitation in composition can be avoided with additional native html tag addition which is not catastrophic but not ideal either. It might also disappear with the apparition of
:has()
css selector.Importance
would make my life easier. However, crucial to talk about at least.
Final words
I don't pretend to offer the perfect solution. But the mechanism I propose has the advantage of meeting a specification that respects the vision of the maintainers while enhancing developer experience :
This mechanism would drastically enhances svelte potential by unblocking component genericity and, at the same time, reduces bad practices such as using
:global()
.Note that my goal is not to revive aggressive debates ! I do not have the energy to reply to aggressive thoughts. I'm not particularly attached to my proposal either, I just want to refocus debates on finding an acceptable solution to this aknowledged problem of styling... Maybe the solution relies in the alternative I've presented which I would happily discuss. I've opened a new issue as I wanted to regroup discussions about styling issues while providing a fresh start and stepping out of the aging huge general issue #6972. However, if the maintainers prefer, I can close it and change it as an answer.
The text was updated successfully, but these errors were encountered: