This repo is the source for the UI Thing documentation site and the copy-paste component library behind it. It is a Nuxt-first, shadcn/ui-inspired system built on Vue 3, Nuxt 4, Reka UI, Tailwind CSS v4, and Nuxt Content.
The important distinction is that this repo does not behave like a typical packaged component library. The app/components/Ui files are the product. The site, APIs, and MCP layer expose those files as editable source.
- Nuxt
4.4.2 - Vue
3.5.x - TypeScript
- Tailwind CSS
4via@tailwindcss/vite tailwind-variantsfor style definitions- Reka UI primitives for accessibility and interaction
- Nuxt Content
3for docs/content motion-vfor animation-heavy docs and blocksvee-validatefor form wrappers@nuxtjs/mcp-toolkit+ custom MCP resources/tools
Treat these as source-of-truth:
app/components/Ui/**app/components/content/prose/**app/components/content/Block/**app/components/content/Docs/**content/**scripts/components.jsscripts/prose.jsscripts/create-components.jsscripts/create-prose.jsscripts/create-blocks.js
Treat these as generated or derived and do not hand-edit unless you are intentionally changing the generator output format:
server/utils/comp.tsserver/utils/prose.tsserver/utils/block-examples.ts.nuxt/**.data/**- build output like
.output/,dist/
If you change UI components, prose components, or blocks, regenerate metadata:
npm run generate:componentsnpm run generate:prosenpm run generate:blocks- or
npm run generate:all
If you change markdown docs that embed example code, expect automd to refresh those blocks. The repo already does this in lint-staged.
app/components/Ui/: core reusable UI Thing componentsapp/components/content/Docs/: small example/demo components used inside docs pagesapp/components/content/Block/: larger copy-paste page sections and block patternsapp/components/content/prose/: global MDC/Nuxt Content prose componentsapp/components/content/ShowCase.global.vue: preview/code switcher used in docscontent/: markdown docs, grouped into getting started, components, goodies, forms, charts, blocks, prose, examplesapp/pages/+app/layouts/: docs site pages and layoutsserver/api/: local JSON/markdown APIs for components, prose, blocks, docs markdownserver/mcp/: MCP tools, prompts, and resources backed by the same generated registriesapp/examples/: non-doc example pagesapp/emails/: email renderer templates
- Use Vue SFCs with
<script setup lang="ts">. - It is common to also include a plain
<script lang="ts">block when exporting reusable types or style definitions. - Prefer
tv()fromtailwind-variantsfor styling. This repo does not rely on a centralcn()helper. - Keep
data-slotattributes on component roots and notable subparts. They are used consistently across the library. - For Reka wrappers, forward primitive props with:
useForwardPropsuseForwardPropsEmitsreactiveOmit
- Accept
class?: HTMLAttributes["class"]on wrapper components and keep the public API aligned with Vue class bindings. - When passing class overrides into
tv()or slot style builders, normalize them at the boundary withnormalizeClass(props.class) || undefinedinstead of narrowing the prop type. - Export reusable styles and variant types when other components depend on them. Example:
buttonStyles. - Use
withDefaults(defineProps<...>(), ...)when defaults exist. - Use
defineSlots,defineExpose, and typed emits where they improve the component contract. - Preserve accessibility behavior from Reka and existing wrappers. Do not strip ARIA, keyboard, focus, or portal behavior for stylistic cleanup.
- UI components use the
Uiprefix and live underapp/components/Ui. - Docs examples use
Docs*names. - Blocks use
Block*names and are grouped by category directories. - Prose components are global and usually end in
.global.vue. - Common aliases in use:
~/for project-root-relative imports@/for app-root-relative imports#componentsfor auto-imported components#appor#app/componentsfor Nuxt types
- Nuxt auto-imports are relied on heavily. Do not add explicit imports for utilities that Nuxt already auto-imports unless necessary.
- Styling is shadcn-like and token-driven.
- The main design tokens live in:
app/assets/css/tailwind.cssapp/assets/css/theme.cssapp/utils/themes.ts
- Prefer semantic tokens like
bg-background,text-foreground,border-border,bg-muted,text-muted-foreground. - Theme selection is done by adding
theme-*classes to the document root. Radius is controlled via CSS custom properties. useConfigStore()persists theme and radius withuseStorage.- Dark mode is handled with
@nuxtjs/color-mode. - Prettier is configured to understand Tailwind classes inside
tv()andtw(). - If you add scoped styles that need Tailwind tokens/utilities, use the repo pattern with
@reference "~/assets/css/tailwind.css"where appropriate.
- Docs content lives in
content/**and is rendered through Nuxt Content. - Every markdown page should have at least
titleanddescriptionfrontmatter. - Section metadata and icons are often declared in
.navigation.yml. - Component docs use MDC custom components such as:
::ShowCase:BlockShowcase:prose-pm-x:SourceCodeLink
- Many docs pages embed source code via
automd:filecomments. Do not manually drift embedded code away from the referenced file. app/pages/[...slug].vueis the main docs renderer./blocks/*pages intentionally suppress the right-side TOC behavior in the page layout.
- Blocks are intentionally visual and often animation-heavy.
- They are expected to compose existing
Ui*components instead of re-implementing primitives. scripts/create-blocks.jsderives required component names by scanning forUi*usage inside block files.app/components/content/Block/BlockShowcase.vueexpects raw block source to be loadable from the file system and is tied to the docs experience.
- Form wrappers live in
app/components/Ui/Vee/**. - They use
vee-validateuseField()instead of ad hoc validation state. - Reusable form field state lives in
app/composables/useFormField.ts. - Keep label, hint, and error message behavior consistent with the existing Vee wrappers.
server/api/**exposes searchable component, prose, block, and markdown endpoints.- Search uses
Fuse.js. server/mcp/**is a first-class part of the product, not throwaway tooling.- MCP tools and resources depend on the generated registries in
server/utils/**, so keep those files regenerated after source changes. server/api/md/[...slug].get.tsconverts content pages back into markdown and appends a GitHub source link. Avoid breaking that assumption when changing content structure.
- Prettier rules:
- 2 spaces
- semicolons on
- double quotes
- trailing commas
es5 - print width
100 - import sorting enabled
- ESLint is based on Nuxt's generated config with a number of relaxed Vue/TS rules.
- Husky hooks are active:
- pre-commit:
npx lint-staged - commit-msg: conventional commits via commitlint
- pre-commit:
lint-stagedruns:automdnpm run generate:allprettier --writeeslint --fixforjs/ts/vue
- Node version target is
>=24.13.1. - Package manager is
npm@11.12.0. .npmrcenableslegacy-peer-deps=true.- Runtime env vars visible in the repo:
PUBLIC_URLGA_ID
- Docker builds expect native deps for
better-sqlite3, then ship the Nuxt server output.
- If you are changing a component API, update the source component first, then regenerate registries.
- If you are changing docs examples, update the demo SFC under
app/components/content/Docs/**and letautomdkeep markdown code snippets aligned. - If you are changing blocks, update the block SFC under
app/components/content/Block/**and regenerateserver/utils/block-examples.ts. - If you are changing prose components, update the
.global.vuesource and regenerateserver/utils/prose.ts. - Avoid editing
.nuxt/**or.data/**directly. - Avoid treating this repo as a packaged library with a single export surface. The file contents themselves are part of the deliverable.
- The open-code, copy-pasteable nature of components
- Shadcn-style design tokens and naming
- Reka accessibility behavior
- Nuxt Content doc structure
- Generated API/MCP metadata staying in sync with source