sveltejs / rfcs

RFCs for changes to Svelte
274 stars 81 forks source link

Allow parent to impact child component CSS #22

Closed AlbertMarashi closed 4 years ago

AlbertMarashi commented 4 years ago

Rendered

Related issues:

stephane-vanraes commented 4 years ago

While I do reckon there is a lot of demand for this, I think having the :global system is preferable especially because it is more verbose. Making it easier to style child components directly would encourage something that is, in my opinion, a bad practice**.

One of the main reasons to work with components is re-usability and portability, but also a delegation of responsibilities. Adding a component should be as easy as simply adding the component without having to know the inner workings (or markup) of this component. A consumer should only be aware of the properties, methods and events of a component. In order to style a child component one has to be aware of the markup as well, which violates this 'delegation of responsibility'-principle.

This also opens the possibility of many unwanted effects when working in teams or with distributed component libraries. A simple example of this would be a button component that renders as follows:

<button>
   <p>
     <slot>
     </slot>
  </p>
</button>

If a consumer decided to change the styling slightly by using:

.wrapper > :global(button > p) { .... }

This will all of suddenly not work anymore when (for whatever reason, this is just an example) the people responsible for the button change the markup to

<button>
   <span>
      <slot></slot>
   </span>
</button>

As you can see, this would mean that a change in a component is potentially no longer isolated to that component, and the entire codebase should be checked to see if anybody has been overriding the default styles. In larger setups this could quickly become an impediment to development. Hence (again: in my opinion) a bad practice.

The :global is an escape hatch because this should only be used in extra-ordinary situations, having it a bit verbose actually aids in preventing this as people are reluctant to use it.

PatrickG commented 4 years ago

For me, the desired use case is not to style a deeply nested child. In your example i would like to style the <button>-element. Change the display property, or give it some margin.

AlbertMarashi commented 4 years ago

@stephane-vanraes I understand what you're getting at, but we shouldn't miss out on useful features just because a few developers will abuse it...

Generic Link component

Imagine I have generic Link component that is supposed to render an anchor tag, and integrate with a javascript router & the logic looks a bit like this,

<a
    href={href}
    class="link"
    class:active={active}
    bind:this={anchor}
    on:click={onClick}>
    <slot/>
</a>
<script>

let router = getContext('router')
let anchor
let href = ''
$: active = router.currentRoute.url === href
async function onClick (event) {
    event.preventDefault()
    await router.push(anchor.getAttribute(href))
}

</script>

This would be a component that is meant to be styled by its parents, and it would make no logical sense to have every possible type of style available inside the Link component.

If I wanted to turn one of these anchor tags into a button, another into a nav-link, and another into a footer link, I should be able to. There's no reason that there shouldn't be a feature to treat a component like a normal HTML element.

:global is not very compatible with preprocessors

In normal CSS, you would write code like so:

wrapper :global(.button) {
   background: white;
}
wrapper :global(.button .icon){
    color: black;
}

But with a preprocessor (such as stylus), you'd want to write something like this:

wrapper :global(.button)
    background white
    .icon
       color black

But unfortunately, that isn't possible, as :global may only be at the beginning or end of a statement (not in the middle)

We don't need to break existing behaviour

The PR lists numerous potential solutions that would not involve breaking existing behaviour. I am fond of the idea of having an attribute for the style tag, such as descendants to target descendants as well, or global to target anything anywhere.

Currently, I'm not able to import a normalize.styl into my Svelte app, because it treats it all as local scope, generating unused CSS warnings. Firstly, even if it was a CSS file that I could link via <Link> style sheet in the <head> I wouldn't want to do this because it increases loading times, as there is a new separate request compared to just compiling the CSS in my svelte component into a single output.

Now, the fact that it is a .styl, I cannot import it into my root layout component. The :global doesn't apply because of the issue I mentioned above.

Lastly, developers don't need to use this feature - If they want to preserve encapsulation & follow component methodology, they can.

:global just feels like a hack for a feature that should already be there.

AlbertMarashi commented 4 years ago

👎

<style lang="stylus">
import 'normalize.styl' //can't make 'normalize' file contents global
</style>

👍


<style lang="stylus" global>
import 'normalise.styl'
</style>
illright commented 4 years ago

For SCSS, it is still possible to maintain some degree of structure with nested :global()s:

.root {
  :global(.child) {
    :global(.desc1) {}      // .root :global(.child .desc1)
    & > :global(.desc2) {}  // .root :global(.child > .desc2)
  }
}

The above compiles just fine and works as expected. However, I do agree that this is verbose.

As for the claim about re-usability, portability and delegation of responsibilities, I believe that this is undesirable in some cases. Pages are components too, and those are not intended to be portable, they are already as large as it gets, and they could benefit strongly from having control over actual reusable components used inside.

blindfish3 commented 4 years ago

To me the suggestion for an attribute on the style tag looks like a really bad solution to this 'problem'. Once that's above the fold it's no longer obvious that your style tag is being applied globally and it will be far too easy to unwittingly add CSS to the wrong style tag.

Your example with the generic link component is also easily solved by using components within the slot that apply styling directly; or just adding the required styling in a global CSS file. I had to spend years working with 'global' (i.e. standard) CSS and have never understood the problem people appear to have with managing scope :shrug:

AlbertMarashi commented 4 years ago

@blindfish3 It's not the only suggestion.

it will be far too easy to unwittingly add CSS to the wrong style tag.

I've never had this happen to me in Vue. Just because a developer might be stupid with the feature doesn't mean it should be blocked.


With the generic link component, you can't use a <slot> because the component needs to apply a directive to the <a> tag, and you can't do that with slots.

Please show me an example of how you would accomplish that in svelte currently. Free to discuss the problem with you in Discord

illright commented 4 years ago

Would also be great to support a more versatile version of the :global() modifier in the form similar to that of a media query:

<style>
  .scoped-style {}

  @global {
    import 'global-stylesheet';

    .global-style {}
  }
</style>

In this way we remedy the problem of not being able to use :global on imported files. Also, if one's using CSS preprocessors, the styling of children could become a lot clearer as follows:

<style lang="scss">
  .something-scoped {
    @global {
      .child-component {
        .descendant-component {}
      }
    }
  }
</style>

This can even be part of svelte-preprocess, as opposed to a native Svelte feature, it will still work nicely.

Benefits

  1. easier to read
    
    @global
    .childComponent
    background: blue;
    h1
      color: red;

// versus

:global(.childComponent) background: blue; :global(.childComponent h1) color: red;

2. syntax highlighting works correctly (and better in some cases than `:global()`)
3. don't need to repeat global for nested styles in preprocessors
```scss
.root {
  @global {
    .child {
      .desc1 {}
      & > .desc2 {}
    }
  }
}

// versus

.root {
  :global(.child) {
    :global(.desc1) {}      // .root :global(.child .desc1)
    & > :global(.desc2) {}  // .root :global(.child > .desc2)
  }
}
  1. allows specifying global for imported stylesheets
AlbertMarashi commented 4 years ago

@illright I love the above approach, it would solve a LOT of problems.

Seems technically possible at first-glance, and resolves many of the concerns that @blindfish3 had.


EDIT:

@illright's solution has really grown on me.

It retains syntax highlighting

image

It supports nesting

//stylus
@global
   .childComponent
      background: blue;
      h1
        color: red;

Easier to read for people that want to use nesting

@global {
   .childComponent {
      background: blue;
      h1 {
        color: red;
      }
   }
}
:global(.childComponent){
   background: blue;
}
:global(.childComponent h1){
   color: red;
}

Doesn't need to repeat :global

Less verbose and easier for developers to write

@global
   .childComponent
      background: blue;
      h1
        color: red;
:global(.childComponent)
   background: blue;
:global(.childComponent h1)
   color: red;

Non-breaking change

This feature can be added without breaking existing functionality

Easy to implement

This would be pretty easy to implement. I would be happy to implement this to svelte.

Makes importing global css files possible

@global
    import 'normalise.styl'

Easier to style descendants

div :global { //scoped global
   .childComponent {
      background: red;
   }
}
illright commented 4 years ago

For a second thought, maybe going with :global {} instead of @global {} is also a considerable option, because this is already a thing in CSS modules: https://github.com/css-modules/css-modules#usage-with-preprocessors

Complying with the CSS modules spec would mean that there already exists a working Stylelint plugin to lint this new addition: https://github.com/pascalduez/stylelint-config-css-modules

AlbertMarashi commented 4 years ago
div :global {
   .foo {
      color: blue;
   }
}

div :global .bar {
   color: red;
}

would transpile to the equivalent of

div :global(.foo){
   color: blue;
}

div :global(.bar){
   color: red;
}
illright commented 4 years ago

Meanwhile, the :global {} syntax is now a part of svelte-preprocess.

AlbertMarashi commented 4 years ago

Lovely, would be great if we could get this added to svelte too 🙏 as not all people may be using svelte-preprocess.

:global selector is much better than :global(selector) anyways since :global() may only be in the beginning or end of a rule (svelte throws an error if it is in the middle)

illright commented 4 years ago

Supporting a point that :global should make it into Svelte.

When using Sass (or any other nesting-enabled CSS preprocessor), one might write code like this:

.local {
  color: red;

  // wrapped in a local selector to prevent style bleed
  :global(.one, .two) {
    color: red;
  }
}

Expected output:

.local.svelte-xxx {
  color: red;
}

.local.svelte-xxx .one,
.local.svelte-xxx .two {
  color: red;
}

Actual output:

.local.svelte-xxx {
  color: red;
}

.local.svelte-xxx .one,
.two {
  color: red;
}

This happened because Sass was unable to split :global(.one, .two) into :global(.one), :global(.two), and thus we ended up with style bleed. This wouldn't have happened if: (a) it was advertized how exactly to use :global() (i.e. show examples that :global(a > b) is valid, but :global(a, b) might produce unexpected results (b) :global() wouldn't exist in function form

Currently, the documentation of :global() is very poor, IMO styling best practices deserve a separate section in the docs.

pretzelhammer commented 4 years ago

Just how we have $$props to access all the props passed to a component I think we should have $$classes to access all the scoped hashed class names from the style block.

Example:

<script>
  import CustomComponent from './CustomComponent.svelte';
</script>

<style>
  .border {
      border: 2px solid black;
  }
</style>

<CustomComponent class="{$$classes.border}"></CustomComponent>

The above doesn't work for another unfortunate reason, it's not possible to write export let class = ''; instead CustomComponent because class is a reserved keyword and isn't allowed to be used as a variable name. The workaround would have to be to use some other prop name like maybe cssClass but then there's no "standard" by which all Svelte components can follow and every library will choose a different name which is cumbersome for users, because it creates scenarios like:

<script>
  import LibOneComponent from 'lib-one/Component.svelte';
  import LibTwoComponent from 'lib-two/Component.svelte';
  import LibThreeComponent from 'lib-three/Component.svelte';
</script>

<style>
  .border {
      border: 2px solid black;
  }
</style>

<!-- without a standard for passing hashed class names everyone will chose random
prop names and users will have to deal with painful scenarios like this one -->
<LibOneComponent cssClass="{$$classes.border}"></LibOneComponent>
<LibTwoComponent classNames="{$$classes.border}"></LibTwoComponent>
<LibThreeComponent classStyle="{$$classes.border}"></LibThreeComponent>

Ideally accepting a prop by the name of class should be supported somehow! That allows for everyone to standardize around class="{$$classes.class}" which gives the best API with the least amount of learning curve to end users.

kaisermann commented 4 years ago

@pretzelhammer You can actually use a class property if you rename it in your export statement:

let className
export { className as class }

Repl: https://svelte.dev/repl/a83454b0e38045708c72e0660b02e1e2?version=3.24.0

illright commented 4 years ago

@pretzelhammer honestly, it's not clear to me how the $$classes thing could work similarly to $$props. Passing classes as props is already possible (with all kinds of prop names), it's just that the hashed component appendix is not exposed in any way. Do you mean that accessing $$classes.someclass would be 'someclass svelte-xxx' with that appendix? Doesn't seem like an intuitive way

stephane-vanraes commented 4 years ago

@pretzelhammer @illright

The problem with the export { className as class } approach is that the classes defined in the parent/calling component still have to be marked as being global otherwise they get removed. The $$classes would just be syntactic sugar over some code that allows a class property on each component with the benefit of these classes not being marked as unused.

pretzelhammer commented 4 years ago

Do you mean that accessing $$classes.someclass would be someclass svelte-xxx with that appendix?

@illright Yes. Both React and Vue solved this problem years ago. Devs need to be to access the hashed classnames in JS as it enables useful patterns like passing scoped styles from a parent to a child without leaking anything into the global scope.

The $$classes would just be syntactic sugar over some code that allows a class property on each component with the benefit of these classes not being marked as unused.

@stephane-vanraes No, I don't think $$classes to be a magic backdoor for defining a class prop on a child component. I think that problem should be solved another way, otherwise it overloads $$classes with too much behavior. I just want $$classes to be a JS Map/Object where the keys are unhashed classnames defined in the style block of the component and the values are the hashed classnames. So given:

<style>
  .some-class {} /* hashes to .some-class-3eu983e */
  .some-other-class {} /* hashes to .some-other-class-mb323 */
</style>

Then I would expect this:

$$classes["some-class"] // returns "some-class-3eu983e"
$$classes["some-other-class"] // returns "some-other-class-mb323"
illright commented 4 years ago

@pretzelhammer but as far as I know, in Svelte it's more like a single hash for the whole component, and the classnames aren't altered.

So if the component had styles:

<style>
  .class1 { color: red; }
  .class2 { color: blue; }
</style>

then the style output would be:

.class1.svelte-xxx { color: red } .class2.svelte-xxx { color: blue }

So the $$classes object would be strange, because all it does is appending the string ' svelte-xxx' to every accessed property.

That said, we could instead opt in specific components into the stylesheet, with some property like svelte:styled or whatever:

Component.svelte

<style>.foo { color: red }</style>

<div class="foo" />

Parent.svelte

<style>.foo { color: blue }</style>

<div class="foo" />
<Component svelte:styled />

And then the rendered output could be:

<style>
  .foo.svelte-yyy { color: red }
  .foo.svelte-xxx { color: blue }
</style>

<div class="foo svelte-xxx"></div>
<div class="foo svelte-yyy svelte-xxx"></div>

Notice how passing the svelte:styled property to the Component made it acquire the parent's scoping class svelte-xxx

pretzelhammer commented 4 years ago

@illright If I'm using a component UI library in my app then svelte:styled would require me to look at the source code of the 3rd-party components in order to be able to customize them from the parent. This is not a good idea for 3 reasons:

  1. It's cumbersome.
  2. A component's internal scoped styles are part of its implementation details and not part of its public interface. As a lib author I would not want my users to know or care what internal class names I'm using. Furthermore, if I change the internal class names then all of my user's apps break. svelte:styled creates a brittle leaky abstraction.
  3. There's no mechanism to pass scoped styles more than 1-level down, e.g. from Parent to GrandChild.

but as far as I know, in Svelte it's more like a single hash for the whole component, and the classnames aren't altered.

As far as I know that's an implementation detail and not a public interface. I don't see why Svelte couldn't change to just hashing the classnames.

illright commented 4 years ago

@pretzelhammer mangling classnames is quite a breaking change. Right now, a lot of existing code is relying on the scoped global (i.e. .scoped :global(.exposed)) to style the children that are otherwise unstyleable. This is only possible if class="exposed" written in Svelte makes it in exactly the same way into the DOM (as opposed to being mangled like class="exposed-deadbeef"). Thus, your proposal could work, but then it would be best to stick to adding a class like it's done currently, except that it won't be the same class for the whole component.

$$classes['some-class'] === 'some-class svelte-xxx-yyy'

where xxx would be a common prefix for the component and yyy would be different for every selector used in the component. That also leads to increased code size.

ggoodman commented 4 years ago

Has any consideration been given to a new 'Component directive' that could look something like:

<script>
  import Child from './child.svelte';
</script>
<style>
  .parent {
    display: flex;
    flex-direction: row;
  }

  .parent > .child {
    flex: 0 0 initial;
  }
</style>

<div class="parent">
  <Child attr:class|append="child"></Child>
</div>

This would be a bit of a blunt instrument that would happily append the child class to all top-level elements of the Child component. This would also need to be express a dependency on the .child class to avoid being optimized away.

illright commented 4 years ago

@ggoodman And how would one style non-top-level elements of the Child component with this approach?

ggoodman commented 4 years ago

I didn't consider that requirement and think that might be both dangerous and surprising for users.

My use-case is limited to parent-child relationships so "going deeper" wasn't motivated by any need.

What kind of use-case have you seen to peek behind the curtain like that @illright?

illright commented 4 years ago

@ggoodman Regarding "dangerous" and "surprising", practically everything in this thread can be categorized this way :p Yet the devs who are aware of what they're doing are still held back.

As for the usecase for going deeper with styling – as I've mentioned earlier here, components like Sapper pages, which are final destinations and not meant to be portable, could really benefit from having full control over what happens with their children.

Consider some component library that you're using inside a page component. There are many cases where you'd want to, say, adjust some spacing inside the library component to fit your page better

ggoodman commented 4 years ago

Thanks for clarifying that makes a lot of sense!

OK, new idea inspired by some of the things that can be done in styled-components and friends...

  1. Top-level elements of components are assigned an opaque, component-specific class name that is internal to the framework. This could perhaps be the class name that is already given to components for the purposes of style encapsulation.
  2. Parents could target any imported component reference in their style using some syntax like :component(Child) { ... }
<script>
  import Child from './child.svelte';
</script>
<style>
  .parent {
    display: flex;
    flex-direction: row;
  }

  .parent > :component(Child) {
    flex: 0 0 initial;
  }
</style>

<div class="parent">
  <Child></Child>
</div>

Would render as:

<div class="svelte-12345 parent">
  <div class="svelte-67890 ..."></div>
</div>
.parent.svelte-12345 {
  display: flex;
  flex-direction: row;
}

.parent.svelte-12345 > .svelte-67890 {
  flex: 0 0 initial;
}

I think that this might be on the path to addressing both deep and immediate parent-child relationships. Since it would be svelte-specific syntax (that appears to NOT be problematic for language tools), the compiler could convert this to an optimized form as shown.

Because this is 'using the platform' (of css), deeper selectors could easily be supported.

ggoodman commented 4 years ago

This would be pretty neat with :global, I think. Imagine being able to have a 'picker' component that shows one of several 'style' components.

Those 'style' components, when mounted would inject global styles that would affect arbitrary referenced components 💥 .

Does this seem to address your use-case @illright?

illright commented 4 years ago

@ggoodman consider the following scenario:

Component.svelte:

<div id="one" />
<div id="two" />

App.svelte:

<script>
  import Component from 'path';
</script>

<style>
  main > :component(Component) {
    color: red;
  }
</style>

<main><Component /></main>

The problem here is – which element gets color: red. The most logical answer would be both, but that is a problem, because I'd want to have a way to target the <div>s in Component independently.

P.S. I didn't get what you meant by the picker and style components, how would that work and what problem would that address

ggoodman commented 4 years ago

I bet we could come up with some opinion that the compiler would be able to either enforce or complain about when targeting multi-root components. Actually targeting div1 or div2 could use :nth-child(n)

pngwn commented 4 years ago

@DominusVilicus Thanks for creating this RFC and thanks to everyone for the discussion, most of which seems to centre around improving the ergonomics of some of the solutions proposed in the RFC.

The problem of styling or theming child components has come up many times before, as illustrated by the many linked issues. While there are solutions to this problem, there is not yet an API that makes this process easy, only an escape-hatch via :global().

Most of the linked issues, as well as this RFC, attempt to solve this problem by relaxing Svelte's CSS scoping rules, providing a better API with which to use global, or by manually passing down classes. We have never found this to be an acceptable solution which is why those issues have been closed. That position has not changed.

Various reasons have been given both in the chatroom and in issues, but I will address this in full here.

Svelte is an opinionated tool; it has opinions about how things should be done and what should be allowed. By adding constraints, we have managed to create a simple API and a performant output. These are often conscious decisions: we don't necessarily agree with historic approaches or how other tools are doing things, and we are happy to push back where we think there may be a better way. This is one of those cases, and I feel that context is important here.

The Problem

CSS encapsulation is important

CSS encapsulation is a critical feature of single file components in Svelte; it allows you to think only about the styles that live together in a given component. Managing CSS has long been one of the more challenging aspects of building for the web; we have no desire to bring those problems back via official APIs that encourage the de-scoping of CSS. We do not wish to revisit the age of namespaced CSS selectors and required preprocessors.

There is an escape hatch in :global() that facilitates this. It is verbose, and we are okay with that, we don't want it to be too pleasant to use. We don't want people doing this at all, but it is there when needed.

Make the right thing easy, and the wrong thing possible.

Components are their own boss

A component should be in complete control of itself. Not only should a component's styles not leak out but other component's style should never leak in. Consider this 'Encapsulation part 2' if you will.

When writing a component, you have certain guarantees that not only will the styles you write be contained within the component, but nothing from the outside should affect you either. You have a certain confidence that if it works in isolation, then it will continue to work when embedded within a complex application.

There are tools in Svelte that break this expectation to a degree, but they are a bit annoying to use, which makes it an active decision on the part of the developer. The API hints at the way we want you to do things because we feel that this will give the better experience.

If a component is to accept styles from outside, then that should be part of the component's API, however that is expressed. It could be props, or it could be css variables, or it could be something else we haven't thought of yet. What we don't want to happen is to bless an approach that inverts this control, allowing an arbitrary parent to impact the inner-workings of a component.

We also believe that this explicit API should be relatively granular and work at the selector or value level, not at the element level. This is why we think classes are "a blunt instrument", as Rich put it. Exposing even a single class gives you a clear entry point to the entire component's markup allowing complete control over every aspect. We need a more granular way for a component to define an explicit style interface.

The proposals are mostly sugar

Most of these proposals relating to global are problematic for the above reasons but, not only that; they don't facilitate anything new.

The :global() selector can already be used to style children; you can even scope its 'globalness' to a subtree via .selector :global(.selector-2) the .selector will be scoped to the class in the component and only affect that subtree.

And they don't solve the biggest issue with global

Not only are they only sugar, but all of the proposed expansions to global also don't address its most significant issue: once you open the gate, it never stops. Global selectors, even when scoped to a subtree, cascade just like regular CSS would. This might be fine for a leaf component, but anywhere else in your app, this is the CSS equivalent of crossing your fingers and hoping that bad things won't happen.

If there was a way to mark a global 'boundary', a point at which the cascade might start and stop, then expanding global as a per-component opt-in might be more reasonable. But I don't know if this would be possible or desirable, it still lacks the granularity mentioned earlier.

We don't want additional runtime checks

Another issue with some CSS-based proposals is that the components don't know whether or not they will be affected. In some cases, there would probably need to be some runtime checks to work this out.

Svelte components are compiled file-by-file; they have no idea how they will be used or what the context will be. They do not know at compile-time, what their parent is doing (not their children). To see whether or not classes or other CSS from the parent is meant to impact it/ should be applied, there may need to be runtime checks on every component, regardless of whether or not they were using the feature. We are pretty strict about trying to avoid features that add cost to components even when those features aren't used.

I'm sure this isn't true for every proposal, but it looks likely for some.

The solution

APIs for composition

Often, allowing the parents to compose elements to be passed into components can offer the flexibility needed to solve this problem. If a component wants to have direct control over every aspect of a component, then it should probably own the markup as well, not just the styles. Svelte's slot API makes this possible.

You can still get the benefits of abstracting certain logic, markup, and styles into a component, but, the parent can take responsibility for some of that markup, including the styling, and pass it through.

This is possible today.

A first-class theming solution

But composition doesn't get you all of the way there. As discussed above, we need a way for a component to define a granular style interface that is configurable from above but that the component remains in complete control of. This API should not open the flood gates of global cascading CSS, and it should offer excellent ergonomics, preferably taking advantage of the recent language tooling developments. We need a theming solution.

This does not exist today. You can do an awful lot with css variables, more than people give them credit for, but this isn't a solved problem. #13 does not address this adequately in my opinion, it improves the ergonomics of something already possible considerably, but doesn't feel sufficient; there are several issues it does not address.

Hopefully, this clarifies our position on using CSS to solve this problem, and we can work together to address the issue of styling without repeating the same discussions.

rgossiaux commented 2 years ago

Components are their own boss

A component should be in complete control of itself. Not only should a component's styles not leak out but other component's style should never leak in. Consider this 'Encapsulation part 2' if you will.

When writing a component, you have certain guarantees that not only will the styles you write be contained within the component, but nothing from the outside should affect you either. You have a certain confidence that if it works in isolation, then it will continue to work when embedded within a complex application.

There are tools in Svelte that break this expectation to a degree, but they are a bit annoying to use, which makes it an active decision on the part of the developer. The API hints at the way we want you to do things because we feel that this will give the better experience.

If a component is to accept styles from outside, then that should be part of the component's API, however that is expressed. It could be props, or it could be css variables, or it could be something else we haven't thought of yet. What we don't want to happen is to bless an approach that inverts this control, allowing an arbitrary parent to impact the inner-workings of a component.

We also believe that this explicit API should be relatively granular and work at the selector or value level, not at the element level. This is why we think classes are "a blunt instrument", as Rich put it. Exposing even a single class gives you a clear entry point to the entire component's markup allowing complete control over every aspect. We need a more granular way for a component to define an explicit style interface.

I want to ask about this because I'm surprised I haven't seen anyone challenge it. I published svelte-headlessui, a port of a very popular React/Vue component library. These are components which are intentionally unstyled. Users are supposed to style them completely. This passage feels to me like it's saying (pretty explicitly) that this is not what a Svelte component should be and that this kind of component is discouraged philosophically by the Svelte team. Is that fair to say?

My problem with the status quo is that your "components are their own boss" principle doesn't allow for a component to choose to delegate its styling to callers. I think my library is a great example of when a component author would want to do that, but I think there are many other cases too (hence all the interest in this and related RFCs and feature requests, etc).

It's a real shame, because on a technical level this problem should be solvable--there just needs to be a way for components to opt in to receiving the scoping class from their parent. The problem seems to be purely ideological. I understand where your point of view comes from (a desire to enforce strict separation at component boundaries, preventing people from doing bad things) but it feels like it's based upon a too narrow idea of what a component can be.

As it is, I'm more or less giving up on the Svelte <style> tag in my personal projects and just using Tailwind.