# Using the widgets in a framework Source: https://selfstore.dev/docs/widgets-frameworks Mounting selfstore's custom elements in React, Vue, Svelte, Angular and plain HTML - the property-versus-attribute rule, the boolean trap that silently keeps a knob on, listening to events, and server rendering. The widgets are custom elements, so every framework can render the tag. What differs between frameworks is **how a value reaches the element**, and that is the whole of the integration story. This page is about mounting the widgets. Binding a store's state to your own components is a different job, covered in [framework bindings](https://selfstore.dev/docs/frameworks). ## The rule that explains everything An element takes values two ways: - **Properties** are JavaScript. They carry objects, functions and real booleans: `store`, `targets`, `options`, `labels`, `icons`, `confirmAction`, `qrProvider`. - **Attributes** are HTML. They carry strings only, and each widget observes a handful of scalar knobs: `armed`, `deferrable`, `variant`, `levels`, `link`, `recommended`, `advanced`, `with-*`. Anything that is not a string **must** go through a property. A framework that writes attributes will stringify your object into `[object Object]` and the widget will ignore it. ## The boolean trap This one is worth a paragraph because it fails silently and no test catches it. The boolean **attributes** are parsed: `deferrable="false"` turns the knob off, and the `with-*` attributes also accept `off`, `no` and `0`. The boolean **properties** are stored as given, with no coercion: ```ts el.deferrable = false; // off el.deferrable = 'false'; // ON - a non-empty string is truthy ``` So in any framework that assigns properties rather than attributes, `deferrable="false"` in your template sets the *property* to the string `"false"`, which is truthy - and the escape hatch you meant to remove is still on screen. Pass a real boolean: ```svelte ``` ## Plain HTML ```html ``` ## React React 19 and later assign a property when the element has one, so JSX props work directly. Earlier versions write attributes, which stringifies objects. The **ref pattern below works on every version**, so it is the one worth learning: ```tsx import { useEffect, useRef } from 'react'; import { defineSelfstoreWidgets } from 'selfstore/widgets'; defineSelfstoreWidgets(); export function Connect({ store }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; el.targets = { file: true, drive: true }; el.store = store; // last const onDone = (e) => console.log(e.detail.outcome); el.addEventListener('selfstore-connected', onDone); return () => el.removeEventListener('selfstore-connected', onDone); }, [store]); return ; } ``` TypeScript needs the tag declared once: ```ts declare module 'react' { namespace JSX { interface IntrinsicElements { 'selfstore-connect': React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; } } } ``` React before 19 does not map custom events to `on*` props either, which is the second reason the ref pattern is the safe default. ## Vue Vue checks whether the key exists on the element and assigns the property when it does, so bindings work as written. Tell the compiler the tag is a custom element so it stops warning about an unknown component: ```js // vite.config.js export default { plugins: [ vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('selfstore-') } } }) ] }; ``` ```vue ``` Add the `.prop` modifier if you ever need to force the property path: `:targets.prop="targets"`. ## Svelte Svelte assigns properties on custom elements, so objects pass through untouched and event listeners work with `on:`: ```svelte console.log('device-only for this session')} >
``` Because Svelte assigns properties, this is exactly where the boolean trap bites: write `deferrable={false}`, never `deferrable="false"`. ## Angular Add `CUSTOM_ELEMENTS_SCHEMA` to the module or component, then bind properties with `[prop]` and listen with `(event)`: ```ts @Component({ schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` ` }) ``` ## Events Every widget event bubbles and is composed, so it crosses the shadow boundary and you can listen on an ancestor rather than on each element: ```ts document.addEventListener('selfstore-connected', (e) => { console.log(e.detail.outcome); }); ``` The full list, with the shape of each `detail`, is on the [events](https://selfstore.dev/docs/events) page. ## Server rendering `defineSelfstoreWidgets()` touches `customElements`, which does not exist on the server. Call it from client-side code only: ```ts if (typeof window !== 'undefined') defineSelfstoreWidgets(); ``` The markup itself is safe to render on the server - an unregistered custom element is an inert unknown tag, and it upgrades as soon as the definition lands in the browser. Two things follow: - give the element a size or a placeholder if a layout shift on upgrade would be visible; - do not expect any widget content in the server HTML - the widgets render into a shadow root, in the browser, after you assign `store`. Map of this site for a model: https://selfstore.dev/llms.txt Every page in one file: https://selfstore.dev/llms-full.txt