# Owned styles

> Copying a primitive CSS file into the application and owning it; the future slean add marked as not available; behavior stays in npm.

Page: https://svelte-lean.vercel.app/styling/owned-styles · Markdown: https://svelte-lean.vercel.app/styling/owned-styles.md · Index: https://svelte-lean.vercel.app/llms.txt

Copy a primitive's stylesheet into the application and edit it there, while the behavior stays a maintained package.

> **Warning** The `slean add` command does not exist yet. The manual copy below is the documented path.

## The boundary

| Owner | What | Why |
| --- | --- | --- |
| Package | Event router, behaviors, keyboard algorithms, focus utilities, the build-time scanner, the table engine | An accessibility fix reaches every project through an npm update. |
| You, once copied | Primitive stylesheets, visual recipes, themes, composition wrappers | Visual source is meant to be edited: no version lock, no unused CSS. |

Copied behavior code goes stale the day a focus bug is fixed upstream; a copied stylesheet does not (ADR 0005).

## Copying a stylesheet

`Terminal`

```bash
# Copy the stylesheet you want to own. tokens.css stays the shared contract the copy depends on;
# copy it as well when you want to own the tokens.
mkdir -p src/lib/slean
cp node_modules/@svelte-lean/styles/css/tabs.css src/lib/slean/tabs.css
```

`+layout.svelte`

```ts
import '@svelte-lean/styles/tokens.css'; // the token contract the copied file reads
import '@svelte-lean/styles/base.css'; // layer order, scoped reset, shared focus ring
import '$lib/slean/tabs.css'; // yours: edit it, the package never touches it again
```

The copy is application source: package updates and the package's static checks no longer reach it. The tabs behavior keeps writing `aria-selected`, `tabindex` and `hidden`, whichever stylesheet reads them.

## Editing the copy

`src/lib/slean/tabs.css`

```css
/* src/lib/slean/tabs.css after an edit. The selectors on protocol attributes and ARIA state
 * stay as they are: the behavior writes aria-selected and hidden, the stylesheet reads them. */
@layer slean.reset, slean.tokens, slean.base, slean.components, slean.compositions, slean.utilities;

@layer slean.components {
	:where([data-slean='tabs'] [data-slean-part='trigger'][aria-selected='true']) {
		border-color: var(--slean-fg); /* was var(--slean-primary-main) */
		font-weight: var(--slean-font-weight-semibold);
	}
}
```

### What to keep

- Selectors on protocol attributes and ARIA state (`[data-slean-part='trigger']`, `[aria-selected='true']`, `[hidden]`): the behavior writes them.
- The `:focus-visible` ring from `base.css`, or your own equivalent.
- The `forced-colors` rules and the coarse-pointer hit areas, or replacements.

### What is safe to change

- Colors, radii, spacing, durations, directly or through the tokens.
- Variants, sizes, the indicator style, transitions.
- The layer statement, the `:where()` wrappers and the layer names.

## Updating a copy

A copy does not update itself. After a package upgrade, compare by hand:

`Terminal`

```bash
# No diff-first update exists yet. After upgrading the package, compare by hand.
diff node_modules/@svelte-lean/styles/css/tabs.css src/lib/slean/tabs.css
```

## The planned command

Planned, not available: copy a stylesheet, record its provenance, explain the primitive's runtime cost, update diff-first. The behavior engine stays package-managed either way.

`Terminal`

```bash
# Not available. The planned command copies the stylesheet, records its provenance and updates
# it diff-first; until it ships, the manual steps above are the documented path.
npx slean add tabs
```

## Escape hatches

- Owned styles → your own CSS against the protocol ([mode 1](https://svelte-lean.vercel.app/styling.md#three-modes)).
- Package tokens → your own custom properties.
- Package stylesheet → an unlayered override ([Themes](https://svelte-lean.vercel.app/styling/themes.md)).

Source: packages/styles/css.
