#
Components
- Component-related TypeScript code (
enums
,interfaces
,types
, etc.) MUST live in the component file itself - Component names should be appended with the corresponding type of component, i.e. a “view” component should be named
SomeComponentView
(the filename should also match) - Props
classes
have to disappear. If we need to style a component, we can wrap up and apply the classes to the wrapper - Platform conditionals should be avoided at all times, and variations should be used instead.
#
Organization
The organization of a Svelte component must start with the script, followed by the markup and the style.
Bad
<script></script>
<style></style>
<Component></Component>
Good
<script></script>
<Component></Component>
<style></style>
#
Reactivity
At the heart of Svelte is a powerful system of reactivity for keeping the DOM in sync with the application state — for example, in response to an event. This section describes our approach to throughout the codebase. Reactive stores (a language feature) assign a store value (app state) to a local variable, and thanks to Svelte reactivity all the markup and reactive dependencies are updated in Svelte Components.
It's not enforced, since regular typescript handles store variables differently. However, the following approach is preferred in Svelte components. Feel free to test the code on the Svelte Playground!
Preferred
<script>
import { writable } from 'svelte/store'
const count = writable(0)
$: double = $count * 2
function onClick(): void {
$count += 1
}
</script>
<h1>
The count is {$count}
The double is {double}
</h1>
<button on:click={onClick}>
Click to update count!
</button>
The same code can be written without using Svelte language features. The following code does the same, albeit being a bit more verbose. This is how store interactions are written in pure typescript. It is accepted, but for Svelte components preference is given to the style described above.
Alternative
<script>
import { onDestroy } from 'svelte'
import { get, writable } from 'svelte/store'
const count = writable(0)
let double = 0
function onClick(): void {
count.update(n => n + 1)
}
const unsubscribe = count.subscribe(() => {
double = get(count) * 2
})
onDestroy(() => {
unsubscribe()
})
</script>
<h1>
The count is {$count}
The double is {double}
</h1>
<button on:click={onClick}>
Click to update count!
</button>
#
Styling
style
attribute should only be used for variablesstyle:--opacity={elementX / screenWidth}
Good
<footer style:--keyboard-height={$isKeyboardOpen ? ($keyboardHeight + 'px') : 0} > <slot name="footer" /> </footer> <style lang="scss"> footer { margin-bottom: var(--keyboard-height); } </style>
Bad
<footer style={$isKeyboardOpen && `margin-bottom: ${$keyboardHeight}px`}> <slot name="footer" /> </footer>
component with multiple variants: the preferred approach is a combination of classes and the style tag, vs creating a string composed of classes generated in TS
Preferred: CSS approach
<script lang="ts"> export let roundedCorners: boolean = false <script lang="ts"> <div class:rounded-corners={roundedCorners}>Box</div> <style lang="scss"> div { &.rounded-corners { @apply rounded-sm; } } </style>
Other: TS approach
<script lang="ts"> export let roundedCorners: boolean = false let classes: string = '' $: $$props, setClasses() function setClasses() { if (roundedCorners) { classes = 'rounded-sm' } else { classes = '' } } <script lang="ts"> <div class={classes}>Box</div>
If a component needs a style that is not inside the tailwind scope, the custom style should be added in the style CSS section
Good
<div>Box</div> <style lang="scss"> div { min-width: 35px; } </style>
Bad
<div style="min-width: 35px;">Box</div>
Max nested children = 1
Good
<button {disabled}> <span class:pink>Button</span> </button> <style lang="scss"> button { @apply bg-blue-50; &:disabled { @apply bg-opacity-50; } } span { &.pink { @apply text-pink-50; } } </style>
Bad
<button {disabled}> <span class:pink>Button</span> </button> <style lang="scss"> button { @apply bg-blue-50; &:disabled { @apply bg-opacity-50; } span { &.pink { @apply text-pink-50; } } } </style>
The preferred way to add styles to a DOM element that does not have variants, is by adding tailwind classes in the
class
attributeGood
<div class="bg-blue-500">Box</div>
Bad
<div>Box</div> <style lang="scss"> button { @apply bg-blue-50; } </style>
The preferred way to add styled to a DOM element that does have variants, is by adding tailwind classes in the
style
sectionGood
<div class:ghost>Box</div> <style lang="scss"> button { @apply bg-blue-50; &.ghost { @apply bg-gray-50; } } </style>
Bad
<div class={ghost ? 'bg-gray-50' : 'bg-blue-50'}>Box</div>
The same DOM element should not contain a combination of tailwind classes (in the
class
attribute) & CSS (in thestyle
section), unless the required styling does not exist in tailwindCustom HTML tags are preferred
Good
<text-hint class="block text-blue-500">This is a hint</text-hint>
Bad
<div class="text-blue-500">This is a hint</div>
When writing tailwind classes, string concatenation should be avoided because the tailwind purging process skips string concatenations
Good
<script lang="ts"> export let primary: boolean = false let backgroundClass: string = getBackgroundClass() function getBackgroundClass(): string { if (primary) { return 'bg-blue-500' } else { return 'bg-pink-500' } } </script> <div class={backgroundClass}>Box</div>
Bad
<div class="bg-{color}-500">Box</div>
#
Prettier
There is a bug in Prettier's Svelte Plugin that replaces nested style and script tags with base58 encoded strings of vanilla JS. Use nested strings in case nested style/script tags are required.
Bad
<iframe
srcdoc={`
<head>
<style type="text/css">
body {
margin: 0px;
}
</style>
</head>
`}
/>
Good
<iframe
srcdoc={`
<head>
<${'style'} type="text/css">
body {
margin: 0px;
}
</style>
</head>
`}
/>