Skip to content
75 changes: 64 additions & 11 deletions src/routes/reference/jsx-attributes/use.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,91 @@ title: use:*
order: 5
---

These are custom directives. In a sense this is just syntax sugar over ref but allows us to easily attach multiple directives to a single element. A directive is a function with the following signature:
Custom directives attach reusable behavior to DOM elements, acting as syntactic sugar over `ref`. They’re ideal for complex DOM interactions like scrolling, tooltips, or form handling, which are cumbersome to repeat in JSX.

A directive is a function with the following signature

```ts
function directive(element: Element, accessor: () => any): void
function directive(element: HTMLElement, accessor: Accessor<any>): void;
```

Directive functions are called at render time but before being added to the DOM. You can do whatever you'd like in them including create signals, effects, register clean-up etc.

## Example

A `model` directive for two-way data binding

```tsx
const [name, setName] = createSignal("")
import type { Accessor, Signal } from "solid-js";

function model(el, value) {
const [field, setField] = value()
createRenderEffect(() => (el.value = field()))
el.addEventListener("input", (e) => setField(e.target.value))
};
function model(element: HTMLInputElement, value: Accessor<Signal<string>>) {
const [field, setField] = value();
createRenderEffect(() => (element.value = field()));
element.addEventListener("input", ({ target }) => setField(target.value));
}

const [name, setName] = createSignal("");

<input type="text" use:model={[name, setName]} />
<input type="text" use:model={[name, setName]} />;
```

To register with TypeScript extend the JSX namespace.
## TypeScript Support

To type custom directives, extend the `DirectiveFunctions` interface

```ts
declare module "solid-js" {
namespace JSX {
interface DirectiveFunctions {
model: typeof model;
}
}
}
```

If you just want to constrain the second argument to the directive function, you can extend the older `Directives` interface

```tsx
declare module "solid-js" {
namespace JSX {
interface Directives {
model: [() => any, (v: any) => any]
model: Signal<string>;
}
}
}
```

## Avoiding Tree-Shaking

When importing a directive `d` from another module and using it only as `use:d`, TypeScript (via [babel-preset-typescript](https://babeljs.io/docs/babel-preset-typescript)) may remove the import, as it doesn’t recognize `use:d` as a reference to `d`.
To prevent this:

1. Use the `onlyRemoveTypeImports: true` option in `babel-preset-typescript`. For `vite-plugin-solid`, add this to `vite.config.ts`

```ts
import solidPlugin from "vite-plugin-solid";

export default {
plugins: [
solidPlugin({
typescript: { onlyRemoveTypeImports: true }
})
],
};
```

Note: This requires consistent use of `export type` and `import type` in your codebase to avoid issues.

2. Add a fake access like `false && d;` in the module

```tsx
import { model } from "./directives";
false && model; // Prevents tree-shaking
<input type="text" use:model={[name, setName]} />;
```

This is removed by bundlers like Terser, unlike a plain `model;` which may remain in the bundle.

:::caution[Limitations]
Directives only work with native HTML elements (HTML/SVG/MathML/Custom Elements).
Directives are not forwarded and **won't work in user defined components**, such as `<MyComponent use:myinput={[..]}/>` [see also](https://github.com/solidjs/solid/discussions/722)
Expand Down