-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Description
Describe the problem
While exploring new ways of providing theming flexibility to a component library, I noticed there isn't an easy way to replace or unload a component's styles without adding extra steps for the developer consuming the library (described in 'alternatives' below).
Describe the proposed solution
I have a few unrefined ideas, and would appreciate any other novel solutions to add to this discussion.
Since the component library comes with styles, I feel the 'happy path' would be importing components onto a page and setting up a single library-provided global.css
file for typography and resets. Users who want to supply their own themes would ideally only have to encounter some trivial configuration.
Proposal 1: pass module paths to compiler options
- User imports component onto page. By default, it comes with CSS in its
<style>
and works without extra configuration. - User can 'unload' all component styles by providing a compiler options such as:
const config = {
css: [
{ 'module-name/ComponentName.svelte': 'none' }, // one component
{ 'library-name', 'none' } // entire library
]
}
This has an all-or-nothing drawback; we cannot specify compiler options on a per-instance-of-component basis.
Proposal 2: dynamic <svelte:options>
I thought about using setContext
via a wrapper component to pass context to set the css
option in <svelte:options>
, but I realize <svelte:options>
should only accept string literals since it cannot know dynamic settings at compile time:
Here's how dynamic <svelte:options> could have looked
+page.svelte*
<script>
import { Button, Theme } from 'component-library'
</script>
<Theme overrideCSS>
<Button>Do the thing</Button>
</Theme>
Button.svelte
<svelte:options css={overrideCSS} />
<script>
import { getContext } from 'svelte'
const overrideCSS = getContext('overrideCSS')
</script>
<button class="btn"><slot /></button>
<style>
.btn { /* ... */ }
</style>
Or perhaps it can be made possible to wrap individual components with <svelte:options>
so that a single instance of the component can be changed, while remaining instances use provided styles.
<svelte:options css="none">
<Button>Button without CSS</Button>
</svelte:options>
<Button>Button with CSS</Button>
Though this would necessarily create a copy of <Button>
at build time. Button styles can be overridden with selectors like :where(.btn)
so that they do not impact plain usage of <Button>
.
Proposal 3: dynamic <style>
Have <style>
load conditionally:
<script>
import { getContext } from 'svelte'
const overrideCSS = getContext('overrideCSS')
</script>
<button class="btn"><slot /></button>
<style load={overrideCSS}>
.btn {/* */}
</style>
But this has the drawback of shipping all bytes of CSS whether or not they're being used.
Alternatives considered
<style>
is completely unused in every component in the library, and users must either import library-supplied CSS or SCSS, or their own CSS.- Only expose certain styles via CSS variables so that users may override via
--style-props
. Any custom theming would have to be provided with global CSS selectors that have high specificity. - Using
setContext
to conditionallyimport()
.css files; but this loads styles onto a page that would impact all instances of a component when only 1 particular instance is meant to be overridden.
Importance
nice to have