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:
import { Toggle } from "@ui-sdk/solid/toggle"
function Example() {
return (
<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>
)
}
export { Example }class, style, and children also accept functions of the part's state. For example, class={(state) => state.pressed ? "font-bold" : "font-normal"}.
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:
import { Toggle } from "@ui-sdk/solid/toggle"
import styles from "./toggle.module.css"
function Example() {
return <Toggle class={styles.toggle}>Bold</Toggle>
}
export { Example }.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;
}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:
import {
Popover,
PopoverClose,
PopoverContent,
PopoverTitle,
PopoverTrigger,
} from "@ui-sdk/solid/popover"
function Example() {
return (
<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>
)
}
export { Example }Put exit styles on data-ending-style. data-closed describes the state but doesn't keep the element around for an exit.
Popups provide --anchor-width and --available-height for sizing. 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, the one your app's entry imports, so your own styles win over it wherever the page puts them. With Tailwind, put it above @import "tailwindcss":
@layer ui-sdk;A CSS Module loads with its 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:
@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>:
<meta property="csp-nonce" nonce="your-nonce" />With Vite, set html.cspNonce in your Vite config to a placeholder, and Vite writes this tag for you. Your server replaces the placeholder with a new nonce on each request.
Pass your nonce to Solid's renderToString or renderToStream too, so Solid's own scripts carry it:
import { generateHydrationScript, renderToString } from "@solidjs/web"
import { App } from "./app"
function page(nonce: string) {
const html = renderToString(() => <App />, { nonce })
return `<!doctype html><html><head><meta property="csp-nonce" nonce="${nonce}">${generateHydrationScript({ nonce })}</head><body><div id="app">${html}</div></body></html>`
}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.
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.