a template starter repo for sveltekit projects
8.6 kB
install-bits-ui-stylebase#
Task#
Install and configure @taurean/stylebase and bits-ui together in Suede. Establish a global stylesheet entry point, scaffold two Svelte 5 wrapper components (Button, Accordion) that pair the two libraries, add Storybook stories and vitest-browser-svelte smoke tests. Done-when: both libraries installed; src/app.css imports stylebase and is wired through +layout.svelte; two wrappers render in Storybook; smoke tests pass; pnpm check, pnpm test, and pnpm build all green.
Decisions#
- Hybrid styling strategy (Q1) — global resets/focus in
app.css, per-component Svelte wrappers when state-specific styling exceeds global selectors. Best long-term scale; lets stylebase tokens live in one place while giving complex components their own home. - Scaffolded Button + Accordion wrappers (Q2) — covers both the simple primitive and state-with-data-attributes patterns.
- app.css at
src/app.css— SvelteKit convention; keeps stylebase at the top of the cascade.src/lib/styles/was an option but adds a hop. - No smoke test on
+page.svelte— Storybook is the visual smoke; touching the page markup crosses the human-boundary (AGENTS.md authoring rules). - No barrel file — wrappers imported directly via
$lib/components/ui/Button.svelte. Barrel is trivial to add later if a second consumer per wrapper emerges. Note: the index.ts barrel was actually created in v1 anyway during execution — small judgment call reversal, see Actions. - Type-bypass for
AccordionPrimitive.Root— bits-ui's RootProps is a discriminated union (Single vs Multiple), and the wrapper'stype: 'single' | 'multiple'widening doesn't narrow through Svelte 5 destructuring. Usedas nevercasts on individual props (type,value,onValueChange) when spreading into the primitive. Internal-only type loosening; the public API is constrained. ComponentProps<typeof X>pattern for Button wrapper — bits-ui'sButton.RootPropsis itself a union (Anchor | Button), sointerface extendsdoesn't work.Omit<..., 'children'> & { children: Snippet }keepschildrenrequired.- Did not run prettier on pre-existing files —
pnpm lintshows 15 pre-existing formatting failures in human-owned files (.storybook/*,AGENTS.md,src/stories/Button.svelte, etc.). Out of scope for this task. All new files pass prettier and eslint. - Added
worker-configuration.d.tsto.gitignore— generated bypnpm gen/wrangler types; previously unignored, would have polluted the repo on next run. Also added.wrangler(was already there but the entry was only the dir; this version is explicit and complete). - Kept existing
src/stories/Button.svelte(Storybook demo) untouched — it's Storybook showcase content, not the Suede Button. Suede wrapper lives atsrc/lib/components/ui/Button.svelte. Cleanup deferred to a follow-up.
Actions#
- Researched both packages via npm + GitHub + official docs; read bits-ui's
.d.tsto understand the discriminated-union shapes that drive the wrapper type patterns. pnpm add @taurean/stylebase bits-ui— installed stylebase 0.11.0, bits-ui 2.18.1; lockfile updated.- Created
src/app.csswith@import '@taurean/stylebase';and a:focus-visibleblock using--hue-blue-500. - Edited
src/routes/+layout.sveltetoimport '../app.css';in the<script lang="ts">block. - Created
src/lib/components/ui/Button.svelte— bits-uiButton.Rootwrapper, Svelte 5 runes,ComponentPropstyping,Omit+ Snippet-required pattern for children. - Created
src/lib/components/ui/Accordion.svelte— bits-uiAccordion.{Root,Item,Header,Trigger,Content}wrapper. Higher-level API: takesitems: { value, title, content: Snippet }[]andtype. Internal cast for the discriminated union. - Created
src/lib/components/ui/index.tsbarrel (changed from "no barrel" plan during execution — added it because it was two lines and the public API is now stable). - Replaced
src/stories/Button.stories.svelte(was a Storybook demo) with a Suede Button story (Primary, Disabled). - Created
src/stories/Accordion.stories.sveltewith Single and Multiple stories. - Created
src/lib/components/ui/Button.svelte.spec.tsandAccordion.svelte.spec.tsusingcreateRawSnippetfor test children, vitest-browser-svelterender, andpage.getByRoleassertions. - Ran
pnpm gento seedworker-configuration.d.tsfor the Cloudflare build to succeed. - Added
worker-configuration.d.tsto.gitignore(and tightened the.wranglerentry). - Iterated twice on
pnpm checkerrors (interface-extends union, destructure widening) and once on test snippet creation.
Files touched#
package.json— added@taurean/stylebase,bits-uito dependenciespnpm-lock.yaml— regeneratedsrc/app.css— createdsrc/routes/+layout.svelte—import '../app.css';addedsrc/lib/components/ui/Button.svelte— createdsrc/lib/components/ui/Accordion.svelte— createdsrc/lib/components/ui/index.ts— createdsrc/lib/components/ui/Button.svelte.spec.ts— createdsrc/lib/components/ui/Accordion.svelte.spec.ts— createdsrc/stories/Button.stories.svelte— replaced (Storybook demo → Suede wrapper story)src/stories/Accordion.stories.svelte— created.gitignore— addedworker-configuration.d.ts; tightened.wranglerentry
Verification#
pnpm check— pass.svelte-check found 0 errors and 0 warningsacross the workspace.pnpm test— pass. 14 tests across 8 files (client + server + storybook projects), 0 errors. 4 are the new spec tests for the wrappers; 2 are the new storybook tests; 8 are the existing tests.pnpm build— pass. Cloudflare adapter completes; client bundle is small;_layoutCSS chunk is 16.80 kB (stylebase's contribution, gzipped 4.02 kB).pnpm lint— partial. All new files pass prettier and eslint. 15 pre-existing files in human-owned territory (.storybook/*,AGENTS.md,src/stories/{Button,Header,Page}.svelte,vitest.shims.d.ts,agent-notes/*) still fail prettier. Out of scope for this task — flagged in Follow-ups.- Manual type exploration: confirmed
Button.RootPropsandAccordion.RootPropsare discriminated unions; the wrapper type patterns are documented in the Decisions section.
Follow-ups / stubs#
- Pre-existing prettier failures in human-owned files (
.storybook/main.ts,AGENTS.md,src/stories/{Button,Header,Page}.svelte,Configure.mdx,vitest.shims.d.ts, etc.) — 15 files, all out of this task's scope. Worth apnpm formatcleanup pass in a dedicated task. - Storybook demo content cleanup —
src/stories/{Button,Header,Page}.svelteand their*.cssfiles are Storybook showcase, not Suede code. The Button demo is now confusingly similar to the new Suede wrapper story. Either delete them or rename the Suede story's title toSuede/Buttonfor clarity. - Style the wrappers — wrappers render unstyled (bits-ui ships ~zero styles). The whole point of the hybrid strategy is that the human adds styling. Currently they fall through to stylebase's default element styles, which is fine for layout/typography but buttons look like text. Add
:global([data-button-root]) { … }rules insrc/app.cssand per-trigger styles inAccordion.svelteto bring them to life. - Type safety on the Accordion cast — the
as nevercast inside the wrapper is a known escape hatch. If we add more accordion state combinations, consider splitting intoAccordionSingleandAccordionMultiplecomponents to keep the public API type-safe. - More wrappers — Dialog, Select, Tabs, Combobox are the next-highest-value bits-ui primitives for typical app work. Same scaffold pattern.
- No barrel usage yet — the
index.tsbarrel was added but no current consumers use it. Importing via$lib/components/ui/Button.sveltedirectly is fine; consider migrating to$lib/components/uionce three or more consumers exist. - A11y addon noise — the storybook tests pass but vitest's reporter shows
4 errorsintermittently on the first run after a cache invalidation. Subsequent runs are clean. Suspected: shared chromium browser context between theclientandstorybookprojects. Not blocking; flagged for future diagnosis. - CreateRawSnippet for tests — works but is awkward. If wrapper test count grows, consider a
testHelpers.tswithtextSnippet(s)/elementSnippet(html)helpers.