codefolio-ui
v1.1.8
Published
Portfolio design library built with Lit Web Components. 15 ready-made components — Navbar, Terminal, Card, Timeline, Sidebar, Table and more. Works with React, Vue, Angular, Svelte, or plain HTML. Dark mode, TypeScript, zero dependencies.
Maintainers
Readme
codefolio-ui
Web Component UI library for React, Vue, Angular, and Svelte — built with Lit, dark mode ready, zero dependencies.
Overview
Docs: https://codefolio-ui.akash-desai.online/
codefolio-ui is a design system built on Lit Web Components. Every component ships as a native custom element, meaning it works in any JavaScript framework or plain HTML — with no runtime coupling to React, Vue, Angular, or Svelte.
- 15 components covering layout, navigation, typography, data display, and inputs
- Dark mode via CSS custom properties and a
data-themeattribute on<html> - React wrappers via
@lit/reactfor React 17/18 (typed props + event forwarding) - React 19+ native custom element support — wrappers optional
- ESM + CJS dual build via
tsup - Full TypeScript — every component ships
.d.tstypes
Requirements
| Requirement | Version | Notes |
|---|---|---|
| Node.js | ≥ 18 | |
| npm | ≥ 7 | Peer deps auto-install on npm 7+ |
| @lit/react | ^1.0.4 | Auto-installed as a peer dependency |
| react + react-dom | ≥ 17 | Only needed for React projects |
@lit/reactis a required peer dependency for React users. On npm 7+, it installs automatically alongsidecodefolio-ui. On npm 6 or older, install it manually (see below).
Installation
React projects (recommended)
Run a single command — npm 7+ will automatically install @lit/react, react, and react-dom as peer dependencies:
npm install codefolio-uiIf you are on npm 6 or older, peer dependencies do not auto-install. Run:
npm install codefolio-ui @lit/react react react-domVerify all three peer deps are present after install:
npm ls @lit/react react react-domVue / Svelte / Angular projects
No peer dependencies needed. Install the core package only:
npm install codefolio-uiPlain HTML / CDN
No install needed — use the jsDelivr CDN directly (see CDN usage below).
Required setup — ThemeProvider
All components depend on CSS design tokens. Without
ThemeProvidermounted once in your app, components will render with no colours, no spacing, and no dark mode.
ThemeProvider injects a <style> tag into <head> containing all CSS custom properties (--cf-primary, --cf-surface, --cf-on-surface, etc.) that every component uses internally.
Place it once at the root of your app, before any other components:
// App.tsx (React)
import { ThemeProvider } from 'codefolio-ui/react'
export default function App() {
return (
<>
<ThemeProvider theme="system" style={{ display: 'none' }} />
{/* rest of your app */}
</>
)
}theme="system"— follows the user's OS preference (light/dark). Also accepts"light"or"dark".style={{ display: 'none' }}— the element renders nothing visible, hide it to avoid layout gaps.- The resolved theme is persisted in
localStorageunder the keycf-themeand survives page reloads. useRefis optional — only needed if you want to callthemeRef.current.toggle()programmatically.
Vue / Svelte / Angular / HTML:
<codefolio-theme-provider theme="system" style="display:none"></codefolio-theme-provider>Usage
React 17 / 18
Import from the /react sub-path. Wrappers are generated with @lit/react so typed props, onX event handlers, and refs all work natively.
import { Button, Card, Navbar } from 'codefolio-ui/react'
import type { ButtonVariant } from 'codefolio-ui/react'
export default function App() {
return (
<>
<Navbar brand="My App" variant="full" position="on-top-always" />
<Card variant="elevated" style={{ padding: '1.5rem' }}>
<Button variant="primary" size="lg" label="Get started" />
</Card>
</>
)
}React 19+
React 19 has native Web Component support. Both patterns are valid:
Option A — React wrappers (full TypeScript types, onX events, refs)
import { Button, Card } from 'codefolio-ui/react'
export default function App() {
return <Button variant="primary" label="Hello" />
}Option B — Native custom elements (no wrappers needed)
import 'codefolio-ui' // registers all custom elements
export default function App() {
return <codefolio-button variant="primary">Hello</codefolio-button>
}Vue / Svelte
Import the main bundle to register all custom elements, then use the HTML tags directly in your templates.
// main.ts / main.js
import 'codefolio-ui'<!-- Vue SFC / Svelte template -->
<codefolio-button variant="primary" label="Hello"></codefolio-button>
<codefolio-card variant="elevated">
<codefolio-text variant="title-lg">Card title</codefolio-text>
</codefolio-card>Angular
Register all custom elements in main.ts, then add CUSTOM_ELEMENTS_SCHEMA to suppress "unknown element" warnings.
// main.ts
import 'codefolio-ui'
import { bootstrapApplication } from '@angular/platform-browser'
import { AppComponent } from './app/app.component'
bootstrapApplication(AppComponent)Standalone component:
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
@Component({
selector: 'app-root',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<codefolio-button variant="primary" label="Hello"></codefolio-button>
<codefolio-card variant="elevated">
<codefolio-text variant="title-lg">Card title</codefolio-text>
</codefolio-card>
`,
})
export class AppComponent {}NgModule (non-standalone):
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { AppComponent } from './app.component'
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
bootstrap: [AppComponent],
})
export class AppModule {}Plain HTML / CDN
<script type="module">
import 'https://cdn.jsdelivr.net/npm/codefolio-ui/dist/index.js'
</script>
<codefolio-button variant="primary">Hello</codefolio-button>
<codefolio-card variant="elevated">
<codefolio-text variant="body-md">Card content</codefolio-text>
</codefolio-card>Theming
Toggle programmatically
Add a ref only if you need to call toggle() from code:
import { ThemeProvider } from 'codefolio-ui/react'
import { useRef } from 'react'
export default function App() {
const themeRef = useRef<any>(null)
return (
<>
<ThemeProvider ref={themeRef} theme="system" style={{ display: 'none' }} />
<button onClick={() => themeRef.current?.toggle()}>Toggle theme</button>
</>
)
}Or toggle directly on the DOM without a ref:
document.documentElement.setAttribute('data-theme', 'dark')
localStorage.setItem('cf-theme', 'dark') // persisted across reloadsListen to changes
<ThemeProvider
theme="system"
onThemeChange={e => console.log(e.detail.theme)} // 'light' | 'dark'
style={{ display: 'none' }}
/>The resolved theme is persisted in localStorage under the key cf-theme and survives page reloads.
For vibe coders
Using an AI coding assistant (Cursor, Claude, Copilot, ChatGPT)? Copy the prompt below and paste it at the start of your chat. It tells the agent to read the ai.md file shipped inside the package — a complete machine-readable API reference with every component, prop, variant, and working example.
You are helping me build a UI using the codefolio-ui component library.
Before writing any code, read the full API reference at:
node_modules/codefolio-ui/ai.md
Key rules from that file:
- Always mount <ThemeProvider theme="system" style={{ display: 'none' }} /> once at the app root — components will not render correctly without it.
- Import React components from 'codefolio-ui/react', not 'codefolio-ui'.
- Button and Chip use a label prop, not children.
- Never invent prop names or variant strings — only use the ones in ai.md.
- Table requires all 7 sub-components (Table, TableHead, TableHeadRow, TableHeadCell, TableBody, TableRow, TableCell).
Once you have read ai.md, confirm and then help me build my UI.The ai.md file is published inside the npm package at node_modules/codefolio-ui/ai.md and is also available on the CDN at https://cdn.jsdelivr.net/npm/codefolio-ui/ai.md.
Components
| Component | Custom element | Description |
|---|---|---|
| Button | <codefolio-button> | 4 variants, 3 sizes, disabled state |
| Chip | <codefolio-chip> | Tag, filled, and outlined chips |
| Card | <codefolio-card> | Surface card — outlined, elevated, filled, glass |
| Text | <codefolio-text> | Full typographic scale — display, title, body, label, eyebrow |
| Navbar | <codefolio-navbar> | 4 layout variants, mobile drawer, theme toggle |
| BoxGroup | <codefolio-box-group> | Bento-grid layout system |
| Section | <codefolio-section> | Full-width page section with 4 background variants |
| Timeline | <codefolio-timeline> | Experience / history timeline |
| Outcomes | <codefolio-outcomes> | Auto-cycling metric cards |
| Sidebar | <codefolio-sidebar> | Fixed scrollspy navigation sidebar |
| Table | <codefolio-table> | Composable data table with muted/good cell variants |
| Terminal | <codefolio-terminal> | Animated terminal command sequence |
| CodeBlock | <codefolio-code-block> | Syntax-highlighted code display |
| ServiceScroller | <codefolio-service-scroller> | Infinite marquee scroller |
| ThemeProvider | <codefolio-theme-provider> | Light / dark / system theming |
Component API examples
Button
<Button variant="primary" size="lg" label="Get started" />
<Button variant="secondary" size="md" label="Learn more" />
<Button variant="ghost" size="sm" label="Cancel" />
<Button variant="cta" label="Book a call" />
<Button variant="primary" label="Disabled" disabled />| Prop | Type | Default | Description |
|---|---|---|---|
| variant | 'primary' \| 'secondary' \| 'ghost' \| 'cta' | 'primary' | Visual style |
| size | 'sm' \| 'md' \| 'lg' | 'md' | Controls padding and font size |
| label | string | — | Button text (required) |
| disabled | boolean | false | Disables click interaction |
| onClick | (e: Event) => void | — | Fires on click |
Card
<Card variant="elevated" style={{ padding: '1.5rem' }}>
<Text variant="title-lg">Title</Text>
<Text variant="body-sm">Description text goes here.</Text>
</Card>| Prop | Type | Default | Description |
|---|---|---|---|
| variant | 'outlined' \| 'elevated' \| 'filled' \| 'glass' | 'outlined' | Surface elevation and style |
| href | string | — | Renders card as <a> with hover/focus styles |
Navbar
const NAV: NavItem[] = [
{ label: 'Home', href: '/' },
{ label: 'About', href: '/about' },
{ label: 'Work', href: '/work' },
{ label: 'Contact', href: '/contact' },
]
<Navbar
variant="full"
position="on-top-always"
brand="My App"
items={NAV}
activeHref={location.pathname}
ctaLabel="Contact"
ctaHref="/contact"
onClick={e => router.push(e.detail.href)}
onThemeToggle={() => toggleDark()}
/>| Prop | Type | Default | Description |
|---|---|---|---|
| variant | 'full' \| 'minimal' \| 'centered' \| 'transparent' | 'full' | Layout and visual style |
| position | 'on-top-always' \| 'scrolled' \| 'static' | 'on-top-always' | CSS positioning strategy |
| brand | string | — | Brand name shown left of nav |
| items | NavItem[] | — | Array of { label, href } nav items (required) |
| activeHref | string | — | Highlights the matching nav item |
| ctaLabel | string | — | Primary CTA button label |
Table
import { Table, TableHead, TableHeadRow, TableHeadCell,
TableBody, TableRow, TableCell } from 'codefolio-ui/react'
<Table>
<TableHead>
<TableHeadRow>
<TableHeadCell>Metric</TableHeadCell>
<TableHeadCell>Before</TableHeadCell>
<TableHeadCell>After</TableHeadCell>
</TableHeadRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Load time</TableCell>
<TableCell variant="muted">4.2s</TableCell>
<TableCell variant="good">1.1s</TableCell>
</TableRow>
</TableBody>
</Table>TableCell variant values: 'default' | 'muted' (strikethrough) | 'good' (highlighted, bold)
Sidebar
import { Sidebar } from 'codefolio-ui/react'
import type { SidebarItem } from 'codefolio-ui/react'
const SECTIONS: SidebarItem[] = [
{ id: 'intro', label: 'Introduction', icon: 'home' },
{ id: 'approach', label: 'Approach', icon: 'lightbulb' },
{ id: 'outcomes', label: 'Outcomes', icon: 'trending_up' },
]
<Sidebar
items={SECTIONS}
title="On this page"
topOffset={80}
onSidebarChange={e => console.log(e.detail.id)}
/>
<section id="intro">...</section>
<section id="approach">...</section>
<section id="outcomes">...</section>The sidebar uses IntersectionObserver to automatically highlight the active section as the user scrolls. Hidden below 1024px — pair with a mobile nav pattern for small screens.
Terminal
import { Terminal } from 'codefolio-ui/react'
import type { TermSequenceItem } from 'codefolio-ui/react'
const SEQ: TermSequenceItem[] = [
{
dir: '~/project',
cmd: 'npm install codefolio-ui',
out: ['added 1 package in 0.8s', 'found 0 vulnerabilities'],
},
{
dir: '~/project',
cmd: 'npm run build',
out: ['⚡ Build success in 74ms', 'dist/index.js 97 KB'],
},
]
<Terminal sequence={SEQ} />Package exports
| Import path | Contents |
|---|---|
| codefolio-ui | All custom elements (registers on import) |
| codefolio-ui/react | React wrappers + TypeScript types |
| codefolio-ui/styles | CSS design token stylesheet |
Development
# Install dependencies
npm install
# Build the library (ESM + CJS)
npm run build
# Watch mode
npm run build:watch
# Type check
npm run type-check
# Lint
npm run lint
# Storybook
npm run storybookBrowser support
All modern browsers that support Web Components v1 (Chrome, Firefox, Safari, Edge). No polyfills required.
License
MIT — see LICENSE for details.
Author
Built by Akash Desai
