Skip to content

$ref() rune for using directives on components #14831

Open
@ottomated

Description

@ottomated

Describe the problem

A commonly requested feature (#12229, #2870, #5236) is to have parity between native elements and svelte components for directives such as class:, style:, use: or transition:. This currently errors:

<Component style:color="red" />
<!-- This type of directive is not valid on components -->

Why is this bad? It breaks intuition for new developers who expect components to behave the same as elements. It makes wrapper components inherently more limited than native elements.

Several points of implementation difficulty have been brought up in the past:

  1. How do you deal with scoped CSS classes?
  2. Which element inside the component do you apply the directives to?
  3. What if multiple elements in a component need to receive the directive?

Describe the proposed solution

The $ref rune would provide a way for components to determine which element receives the directives.

<script>
  const props = $props();
  let ref = $ref();
</script>

<button bind:this={ref} {...props}></button>

Any component that does not explicitly use $ref() still throws an error when a directive is used on it.

I believe this answers the main issues on this topic.

  1. How do you deal with scoped CSS classes?

Right now, if you pass a class prop to a component, it uses the scoped css hash from that component (playground). class: would behave the same way, i.e. <Comp class:foo /> would use the .foo defined in Comp.svelte.

  1. Which element inside the component do you apply the directives to?

This proposal leaves the choice to the component creator. They could specify which of the root elements receives the directives, or a non-root element, or a child component like this:

<!-- Parent.svelte -->
<Child use:action />

<!-- Child.svelte -->
<script>
  const ref = $ref();
</script>
<Grandchild bind:this={ref} />

<!-- Grandchild.svelte -->
<script>
  const ref = $ref();
</script>
<div bind:this={ref} />
<!-- this element receives the action -->
  1. What if multiple elements in a component need to receive the directive?

In this proposal, it would be impossible. However, here are some alternatives:

Alternatives

  1. Use the rune only inside the property:
<div bind:this={$ref()}></div>

This doesn't allow using the bind:this for a local use, though.

  1. Don't use bind:this
<script>
  let canvas;
  const ref = $ref();
</script>
<canvas bind:this={canvas} {ref}></canvas>

Could also be svelte:ref={ref} or something.

  1. Allow multiple $ref()s per component
<script>
  let ref1 = $ref();
  let ref2 = $ref();
</script>
<div bind:this={ref1}></div>
<div bind:this={ref2}></div>

In this case, the directives would be duplicated and both elements would receive them.

I'm interested in knowing if there's some other barrier that makes this impossible, but I haven't found one while researching this issue!

Importance

would make my life easier

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions