Package Exports
- codefolio-ui
- codefolio-ui/react
- codefolio-ui/styles
Readme
codefolio-ui
Web Component UI library for React, Vue, Angular, and Svelte — built with Lit, dark mode ready, zero dependencies.
Overview
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.
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