# Styling and Animation

Style parts and their state, then add entry and exit transitions.

Parts render without styles. The component demos use plain CSS, and the same state attributes and CSS variables work with Tailwind CSS or any other stylesheet.

## Style a Part

Every part has a `data-slot` attribute. State adds attributes such as `data-pressed` and `data-disabled`:

```svelte
<script lang="ts">
  import { Toggle } from "@ui-sdk/svelte/toggle"
</script>

<Toggle
  class="rounded-md border border-neutral-300 px-2.5 py-1.5 text-sm data-pressed:bg-neutral-100 data-disabled:opacity-50"
  >Bold</Toggle
>
```

Most state attributes are present or absent. Others have values, such as `data-side="top"`. Each component's Styling section lists its attributes and CSS variables. Use those rather than generated IDs.

## CSS Modules

Import a CSS Module and pass its class to the part. State selectors work the same way:

```svelte
<script lang="ts">
  import { Toggle } from "@ui-sdk/svelte/toggle"
  import styles from "./toggle.module.css"
</script>

<Toggle class={styles.toggle}>Bold</Toggle>
```

```css title="toggle.module.css"
.toggle {
  cursor: default;
  border: 1px solid var(--input, #e5e5e5);
  border-radius: 1.125rem;
  background: transparent;
  padding: 0.375rem 0.625rem;
  color: inherit;
  font: inherit;
  font-size: 0.875rem;
}

.toggle[data-pressed] {
  background: var(--muted, #f5f5f5);
}

.toggle[data-disabled] {
  opacity: 0.5;
}

.toggle:focus-visible {
  outline: 2px solid var(--ring, #a3a3a3);
  outline-offset: 2px;
}
```

You can also use Svelte's scoped styles. When a selector targets markup inside a component, use `:global`, such as `:global([data-slot="toggle"])`. CSS Modules avoid that scoping boundary.

## Animate Opening and Closing

Popup content gets `data-starting-style` before it enters and `data-ending-style` while it exits. It stays mounted until the exit transition or animation finishes:

```svelte
<script lang="ts">
  import {
    Popover,
    PopoverClose,
    PopoverContent,
    PopoverTitle,
    PopoverTrigger,
  } from "@ui-sdk/svelte/popover"
</script>

<Popover>
  <PopoverTrigger>Details</PopoverTrigger>
  <PopoverContent
    class="origin-(--transform-origin) transition-[opacity,scale] duration-150 data-starting-style:scale-96 data-starting-style:opacity-0 data-ending-style:scale-96 data-ending-style:opacity-0 motion-reduce:transition-none"
  >
    <PopoverTitle>Project Details</PopoverTitle>
    <p>Last updated today.</p>
    <PopoverClose>Done</PopoverClose>
  </PopoverContent>
</Popover>
```

Put exit styles on `data-ending-style`. `data-closed` describes the state but doesn't keep the element around for an exit.

### Svelte Transitions

Svelte's own transitions work too. Put one on the element you render, with `|global`, since the block that adds and removes the element is inside the component. A popup stays visible above the page until its outro ends:

```svelte
<script lang="ts">
  import {
    Popover,
    PopoverContent,
    PopoverTrigger,
  } from "@ui-sdk/svelte/popover"
  import { fade } from "svelte/transition"
</script>

<Popover>
  <PopoverTrigger>Details</PopoverTrigger>
  <PopoverContent aria-label="Project details">
    {#snippet render({ props })}
      <div {...props} transition:fade|global={{ duration: 150 }}>
        Last updated today.
      </div>
    {/snippet}
  </PopoverContent>
</Popover>
```

Popups provide `--anchor-width` and `--available-height` for sizing. [Popups](/docs/svelte/popups) covers mounting and placement.

## The ui-sdk Layer

A few behaviors need a little CSS, like keeping a closed popup hidden or dimming the page behind a sheet, so ui-sdk adds it to the page in a cascade layer named `ui-sdk`. Declare that layer at the top of your global stylesheet, like the `app.css` your root `+layout.svelte` imports, so your own styles win over it wherever the page puts them. With Tailwind, put it above `@import "tailwindcss"`:

```css
@layer ui-sdk;
```

A component's `<style>` block loads with the component, too late to come first, so keep the line in the global stylesheet.

ui-sdk adds this CSS once a component mounts, so HTML rendered on the server doesn't have it until the page hydrates. Closed content kept in the page, like a tooltip's, carries `hidden`, and a class that sets `display` shows it until then. Tailwind's base styles already hide `hidden` elements. With other CSS, add the same rule to your global stylesheet:

```css
@layer ui-sdk {
  :where([data-slot][hidden]:not([hidden="until-found"])) {
    display: none !important;
  }
}
```

It leaves `hidden="until-found"` alone, so find in page can still open a closed panel.

## Content Security Policy

A Content Security Policy without `'unsafe-inline'` in `style-src` blocks the CSS ui-sdk adds. Give ui-sdk your policy's nonce with a meta tag in the page's `<head>`:

```html
<meta property="csp-nonce" nonce="your-nonce" />
```

If your policy has no nonce for styles, add the tag without the `nonce` attribute. ui-sdk then adds its CSS as constructed style sheets, which no policy blocks. If your code replaces `document.adoptedStyleSheets`, ui-sdk adds its sheets back the next time a component mounts.

In SvelteKit, put the tag without a nonce in `src/app.html`. It works with any `kit.csp` mode, on prerendered pages too, as long as `@layer ui-sdk;` comes first in your global stylesheet:

```html
<meta property="csp-nonce" />
```

SvelteKit's `nonce="%sveltekit.nonce%"` works only when `style-src` carries that nonce, and SvelteKit adds it there only for CSS it inlines itself, which `kit.inlineStyleThreshold` turns on. The placeholder also stops a page from being prerendered.

With Vite alone, set `html.cspNonce` in your Vite config to a placeholder, and Vite writes the tag for you. Your server replaces the placeholder with a new nonce on each request.

Keep Svelte's default `css: "external"` under a strict policy, since `css: "injected"` adds each component's styles as a `<style>` without a nonce.

Pages rendered on the server also carry a few `style` attributes, like the one that hides a select's native input, so the policy needs `style-src-attr 'unsafe-inline'` too.
