<!-- Source: https://scoped.svelte.page/docs -->

# Get started with Svelte Scoped Props

> Use explicit scoped: props to pass parent-scoped Svelte classes through component props.

**Source:** [https://scoped.svelte.page/docs](https://scoped.svelte.page/docs)

---

**Svelte Scoped Props** adds an explicit `scoped:<prop>` directive for component props.
It is for the narrow case where the parent owns a scoped CSS selector, but the element
that needs the class lives inside a child component.

```svelte
<ChildCard scoped:class="parent-owned" />
<MotionBox scoped:class={['panel', { active }]} />
<FancyCard scoped:internalClass="inner-panel" />
```

Before Svelte compiles the component, the preprocessor rewrites the directive into a
normal prop value with the parent component's CSS hash attached.

```svelte
<ChildCard class="parent-owned svelte-abc123" />
```

The child still controls what happens next. If it applies `class` to an internal
element, that internal element receives the parent-scoped class. If it treats `class`
as data, Svelte Scoped Props does not force it to become CSS.

## Install

```bash
pnpm add -D @humanspeak/svelte-scoped-props
```

## Configure Svelte

Add the preprocessor after your style preprocessors so it sees the same CSS Svelte
will hash.

```ts title="svelte.config.ts"
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import { scopedProps } from '@humanspeak/svelte-scoped-props'

export default {
    preprocess: [
        vitePreprocess(),
        scopedProps({
            runtimeModule: '@humanspeak/svelte-scoped-props/runtime'
        })
    ]
}
```

The `runtimeModule` option points dynamic `ClassValue` props at the published scoped
package runtime. Literal props do not import the runtime helper.

## First scoped prop

```svelte title="Parent.svelte"
<script lang="ts">
    import ChildCard from './ChildCard.svelte'
</script>

<ChildCard scoped:class="parent-owned" />

<style>
    .parent-owned {
        color: purple;
    }
</style>
```

```svelte title="ChildCard.svelte"
<script lang="ts">
    import type { ClassValue } from 'svelte/elements'

    interface Props {
        class?: ClassValue
    }

    let props: Props = $props()
</script>

<article class={props.class}>
    Child content
</article>
```

The class is authored in `Parent.svelte`, but the child can decide where it lands.
The generated prop contains the parent scope hash, so the parent style can match the
child's internal element.

## Dynamic values

Dynamic scoped props accept the same `ClassValue` shapes used by Svelte class
attributes: strings, arrays, and object maps.

```svelte
<script lang="ts">
    import ChildCard from './ChildCard.svelte'

    let active = $state(true)
    const classes = $derived(['parent-owned', { active }])
</script>

<ChildCard scoped:class={classes} />
```

The preprocessor routes dynamic values through a tiny runtime helper that normalizes
the value and appends the scope hash.

## Prop aliases

The prop name comes after `scoped:`.

```svelte
<FancyCard scoped:internalClass="inner-panel" />
```

That rewrites to an `internalClass` prop. The child can expose whatever class-like
prop names make sense for its API.

## Spread forwarding

`scoped:` intent must be expressed before a spread object exists.

```svelte
<MiddleChild scoped:class="parent-owned" />
```

After the preprocessor rewrites the prop, a middle component can forward it like any
other prop.

```svelte
<script lang="ts">
    let props = $props()
</script>

<FinalChild {...props} />
```

A raw object spread cannot contain `scoped:` syntax, so scope first and spread later.

## What this is not

- It is not a Svelte core feature.
- It does not make every component `class` prop special.
- It does not inspect child component TypeScript types.
- It does not cross language boundaries where Svelte markup is no longer available.

If CI runs `svelte/valid-compile`, add the [ESLint processor](/docs/eslint) so lint
sees the same transform as your Svelte build. For the full package surface, see the
[API Reference](/docs/api-reference). For the current caveats, see [Limits](/docs/limits).
