9.5 KiB
title |
---|
Bindings |
- how for dom elements
- list of all bindings
- how for components
Most of the time a clear separation between data flowing down and events going up is worthwhile and results in more robust apps. But in some cases - especially when interacting with form elements - it's more ergonomic to declare a two way binding. Svelte provides many element bindings out of the box, and also allows component bindings.
bind:property for elements
<!--- copy: false --->
bind:property={variable}
Data ordinarily flows down, from parent to child. The bind:
directive allows data to flow the other way, from child to parent. Most bindings are specific to particular elements.
The simplest bindings reflect the value of a property, such as input.value
.
<input bind:value={name} />
<textarea bind:value={text} />
<input type="checkbox" bind:checked={yes} />
If the name matches the value, you can use a shorthand.
<input bind:value />
<!-- equivalent to
<input bind:value={value} />
-->
Numeric input values are coerced; even though input.value
is a string as far as the DOM is concerned, Svelte will treat it as a number. If the input is empty or invalid (in the case of type="number"
), the value is undefined
.
<input type="number" bind:value={num} />
<input type="range" bind:value={num} />
On <input>
elements with type="file"
, you can use bind:files
to get the FileList
of selected files. When you want to update the files programmatically, you always need to use a FileList
object. Currently FileList
objects cannot be constructed directly, so you need to create a new DataTransfer
object and get files
from there.
<script>
let files = $state();
function clear() {
files = new DataTransfer().files; // null or undefined does not work
}
</script>
<label for="avatar">Upload a picture:</label>
<input accept="image/png, image/jpeg" bind:files id="avatar" name="avatar" type="file" />
<button onclick={clear}>clear</button>
FileList
objects also cannot be modified, so if you want to e.g. delete a single file from the list, you need to create a new DataTransfer
object and add the files you want to keep.
DataTransfer
may not be available in server-side JS runtimes. Leaving the state that is bound tofiles
uninitialized prevents potential errors if components are server-side rendered.
If you're using bind:
directives together with on
event attributes, the binding will always fire before the event attribute.
<script>
let value = 'Hello World';
</script>
<input oninput={() => console.log('New value:', value)} bind:value />
Here we were binding to the value of a text input, which uses the input
event. Bindings on other elements may use different events such as change
.
Binding <select>
value
A <select>
value binding corresponds to the value
property on the selected <option>
, which can be any value (not just strings, as is normally the case in the DOM).
<select bind:value={selected}>
<option value={a}>a</option>
<option value={b}>b</option>
<option value={c}>c</option>
</select>
A <select multiple>
element behaves similarly to a checkbox group. The bound variable is an array with an entry corresponding to the value
property of each selected <option>
.
<select multiple bind:value={fillings}>
<option value="Rice">Rice</option>
<option value="Beans">Beans</option>
<option value="Cheese">Cheese</option>
<option value="Guac (extra)">Guac (extra)</option>
</select>
When the value of an <option>
matches its text content, the attribute can be omitted.
<select multiple bind:value={fillings}>
<option>Rice</option>
<option>Beans</option>
<option>Cheese</option>
<option>Guac (extra)</option>
</select>
Elements with the contenteditable
attribute support the following bindings:
There are slight differences between each of these, read more about them here.
<div contenteditable="true" bind:innerHTML={html} />
<details>
elements support binding to the open
property.
<details bind:open={isOpen}>
<summary>Details</summary>
<p>Something small enough to escape casual notice.</p>
</details>
Media element bindings
Media elements (<audio>
and <video>
) have their own set of bindings — seven readonly ones...
duration
(readonly) — the total duration of the video, in secondsbuffered
(readonly) — an array of{start, end}
objectsplayed
(readonly) — dittoseekable
(readonly) — dittoseeking
(readonly) — booleanended
(readonly) — booleanreadyState
(readonly) — number between (and including) 0 and 4
...and five two-way bindings:
currentTime
— the current playback time in the video, in secondsplaybackRate
— how fast or slow to play the video, where 1 is 'normal'paused
— this one should be self-explanatoryvolume
— a value between 0 and 1muted
— a boolean value indicating whether the player is muted
Videos additionally have readonly videoWidth
and videoHeight
bindings.
<video
src={clip}
bind:duration
bind:buffered
bind:played
bind:seekable
bind:seeking
bind:ended
bind:readyState
bind:currentTime
bind:playbackRate
bind:paused
bind:volume
bind:muted
bind:videoWidth
bind:videoHeight
/>
Image element bindings
Image elements (<img>
) have two readonly bindings:
naturalWidth
(readonly) — the original width of the image, available after the image has loadednaturalHeight
(readonly) — the original height of the image, available after the image has loaded
<img
bind:naturalWidth
bind:naturalHeight
></img>
Block-level element bindings
Block-level elements have 4 read-only bindings, measured using a technique similar to this one:
clientWidth
clientHeight
offsetWidth
offsetHeight
<div bind:offsetWidth={width} bind:offsetHeight={height}>
<Chart {width} {height} />
</div>
bind:group
<!--- copy: false --->
bind:group={variable}
Inputs that work together can use bind:group
.
<script>
let tortilla = 'Plain';
/** @type {Array<string>} */
let fillings = [];
</script>
<!-- grouped radio inputs are mutually exclusive -->
<input type="radio" bind:group={tortilla} value="Plain" />
<input type="radio" bind:group={tortilla} value="Whole wheat" />
<input type="radio" bind:group={tortilla} value="Spinach" />
<!-- grouped checkbox inputs populate an array -->
<input type="checkbox" bind:group={fillings} value="Rice" />
<input type="checkbox" bind:group={fillings} value="Beans" />
<input type="checkbox" bind:group={fillings} value="Cheese" />
<input type="checkbox" bind:group={fillings} value="Guac (extra)" />
bind:group
only works if the inputs are in the same Svelte component.
bind:this
<!--- copy: false --->
bind:this={dom_node}
To get a reference to a DOM node, use bind:this
.
<script>
import { onMount } from 'svelte';
/** @type {HTMLCanvasElement} */
let canvasElement;
onMount(() => {
const ctx = canvasElement.getContext('2d');
drawStuff(ctx);
});
</script>
<canvas bind:this={canvasElement} />
Components also support bind:this
, allowing you to interact with component instances programmatically.
<!--- App.svelte --->
<ShoppingCart bind:this={cart} />
<button onclick={() => cart.empty()}> Empty shopping cart </button>
<!--- ShoppingCart.svelte --->
<script>
// All instance exports are available on the instance object
export function empty() {
// ...
}
</script>
Note that we can't do
{cart.empty}
sincecart
isundefined
when the button is first rendered and throws an error.
bind:property for components
bind:property={variable}
You can bind to component props using the same syntax as for elements.
<Keypad bind:value={pin} />
While Svelte props are reactive without binding, that reactivity only flows downward into the component by default. Using bind:property
allows changes to the property from within the component to flow back up out of the component.
To mark a property as bindable, use the $bindable
rune:
<script>
let { readonlyProperty, bindableProperty = $bindable() } = $props();
</script>
Declaring a property as bindable means it can be used using bind:
, not that it must be used using bind:
.
Bindable properties can have a fallback value:
<script>
let { bindableProperty = $bindable('fallback value') } = $props();
</script>
This fallback value only applies when the property is not bound. When the property is bound and a fallback value is present, the parent is expected to provide a value other than undefined
, else a runtime error is thrown. This prevents hard-to-reason-about situations where it's unclear which value should apply.