parent
f763cca722
commit
efebfa3889
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"cSpell.words": ["customizability"]
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import Button from '$lib/Button.svelte';
|
import Button from '$lib/Button.svelte';
|
||||||
import Spacer from '$lib/Spacer.svelte';
|
import Spacer from '$lib/Spacer.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
125
src/routes/posts/2024.05.12.svelte-customizable-components.md
Normal file
125
src/routes/posts/2024.05.12.svelte-customizable-components.md
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
---
|
||||||
|
title: Making Customizable Components in Svelte
|
||||||
|
date: 2024-05-12T17:50:52+0000
|
||||||
|
tags:
|
||||||
|
- dev
|
||||||
|
- svelte
|
||||||
|
- typescript
|
||||||
|
description: Making customizable components in Svelte can be hard, but check out this post on how to use Svelte with TailwindCSS to make them possible at no performance penalty.
|
||||||
|
---
|
||||||
|
|
||||||
|
I really like [Svelte](https://svelte.dev), but it has one big limitation
|
||||||
|
in my opinion: it's hard to make customizable, reusable components.
|
||||||
|
|
||||||
|
Let me demonstrate what I mean using a React example. In React, I would
|
||||||
|
typically use one of the many ubiquitous UI libraries such as [Chakra UI](https://v2.chakra-ui.com).
|
||||||
|
Using this library, I'd write some code like this:
|
||||||
|
|
||||||
|
<iframe src="https://codesandbox.io/p/devbox/vqyf4v?view=editor+%2B+preview&file=%2FApp.tsx" title="loving-meitner-vqyf4v" />
|
||||||
|
|
||||||
|
Svelte's CSS selectors don't apply to custom components, so you can't just
|
||||||
|
write CSS targeting Unless you want to add inline styles to your components
|
||||||
|
--which have limitations of their own such as no media queries-- you can't
|
||||||
|
really do something like this in Svelte though.
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
import MyButton from './MyButton.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<MyButton>Hello!</MyButton>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Does not work! */
|
||||||
|
MyButton {
|
||||||
|
background-color: #5e548e;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
I mean, that's a bit misleading. You can do what UI libraries like Chakra do
|
||||||
|
under the hood, which is using CSS-in-JS to dynamically generate your CSS at
|
||||||
|
runtime. This comes at the [cost of worse performance and more complexity](https://dev.to/srmagura/why-were-breaking-up-wiht-css-in-js-4g9b) though,
|
||||||
|
so it's not a silver bullet.
|
||||||
|
|
||||||
|
Using [TailwindCSS](https://tailwindcss.com) though, you can get around this
|
||||||
|
issue. TailwindCSS uses a compilation step that reads through the `class`es in
|
||||||
|
your HTML (or svelte) files, and generates CSS to match the utility classes you
|
||||||
|
used. This is great for us because TailwindCSS doesn't really care if it's a
|
||||||
|
regular HTML component or a custom component where we write the classes. So you
|
||||||
|
can do something like this:
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<!-- In MyButton.svelte -->
|
||||||
|
<script>
|
||||||
|
export let className = '';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button class={className}><slot /></button>
|
||||||
|
|
||||||
|
<!-- In App.svelte -->
|
||||||
|
<script>
|
||||||
|
import MyButton from './MyButton.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- This does work! -->
|
||||||
|
<MyButton className="bg-indigo-900">Hello!</MyButton>
|
||||||
|
```
|
||||||
|
|
||||||
|
Okay great, but, my custom component basically does nothing right now. I haven't
|
||||||
|
actually customized anything beyond what a regular button is like. Can we do
|
||||||
|
that? Yes! We just need to join the "default" classes together with the classes
|
||||||
|
being passed in. TailwindCSS understands this.
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script lang="ts">
|
||||||
|
export let className = '';
|
||||||
|
|
||||||
|
/** Combine classes together.
|
||||||
|
*
|
||||||
|
* Also allows falsy values, so you can do things like
|
||||||
|
* clsx(errorCondition ?? "color-error")
|
||||||
|
*/
|
||||||
|
function clsx(...args: (string | undefined | null | false)[]) {
|
||||||
|
return args.filter((arg) => !!arg).join(' ');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- TailwindCSS understands this, and will generate the correct classes -->
|
||||||
|
<button class={clsx('text-white min-w-32 bg-blue-900', className)}><slot /></button>
|
||||||
|
```
|
||||||
|
|
||||||
|
I want to do one final improvement though. While we can do `<button class="...">` for regular HTML components, our custom components have to use
|
||||||
|
`<MyButton className="...">`. This is a bit annoying, because you have to switch between the two within your codebase.
|
||||||
|
Thankfully, Svelte comes to our rescue here and lets us rename exported properties. We can do that like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
let className = '';
|
||||||
|
export { className as class };
|
||||||
|
```
|
||||||
|
|
||||||
|
And then you'll be able to do `<MyButton class="...">` as well!
|
||||||
|
|
||||||
|
Let's put all of this together in one example:
|
||||||
|
|
||||||
|
<iframe src="https://codesandbox.io/p/devbox/practical-banach-lphctd?file=%2Fsrc%2Flib%2FMyButton.svelte&embed=1" />
|
||||||
|
|
||||||
|
Combined with a TailwindCSS component library like
|
||||||
|
[DaisyUI](https://daisyui.com), this makes for a really amazing developer
|
||||||
|
experience that doesn't sacrifice performance or maintainability.
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../vars';
|
||||||
|
|
||||||
|
$extra-width: min(50%, 100vw - min($size-container, 100vw));
|
||||||
|
$height: 800px;
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
box-shadow: $shadow-medium;
|
||||||
|
width: calc(100% + $extra-width);
|
||||||
|
max-width: 100vw;
|
||||||
|
margin-left: calc($extra-width / -2);
|
||||||
|
border: 0;
|
||||||
|
height: $height;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in a new issue