OverviewGetting startedComponentsIconsGuidesExamples
OverviewGetting startedComponentsIconsGuidesExamples
GitHub repositoryJoin the c2n-webcomponents Discord

Frameworks

Use the components from React, Vue, Angular, Svelte, Lit or plain HTML — typed events, generated template types, and the one piece of configuration each framework needs.

Every c2-* component is a standard custom element, so nothing here is a wrapper: the same element runs in every framework. What differs is how each one decides between an attribute and a property, and how it binds an event whose name has a hyphen in it. This guide is the short version of the example apps — React, Vue, Angular and SvelteKit — which are complete applications built the way an outside project would build one.

Styling slots and public parts

There are two independent styling routes. A node you place in a slot remains consumer-owned light DOM, so give it a class and style it with normal framework-scoped CSS. The layout, fallback, or state region created around that slot belongs to the component’s shadow root; style it only through a documented ::part() name or CSS custom property.

A bare text node is the exception to “give it a class”: text cannot be targeted directly. Use the host’s inherited typography or documented custom properties when that is the component contract—as with c2-mat-icon ligature text—or wrap the text in an element you own when it needs independent styling.

.message-copy { font-weight: 500; } /* assigned node */
c2-chat-message::part(content) { padding: 12px; } /* component-owned region */

Parts do not cross another shadow boundary. A content part can style the region containing a slotted c2-avatar, but cannot reach the avatar’s internals; use the avatar’s own public contract. In Vue SFCs, keep ::part() rules global because the scoped attribute cannot be added inside a component shadow root. Angular Emulated encapsulation has the same constraint: place host ::part() selectors in a global stylesheet (or opt that owning component out of emulated encapsulation), while ordinary classes on assigned nodes may remain local.

The support-inbox Vue example demonstrates both paths: thread__message-copy styles the assigned <p>, while the global .thread__message::part(content) rule styles its component-owned container without piercing the nested avatar.

Two rules hold everywhere:

  • Register the elements at module scope, before the framework renders. Frameworks decide “attribute or property” by looking for the property on the element, and an element that has not upgraded yet has none — an object handed to rows would be stringified into an attribute.
  • Load the theme once. import '@c2n/components/theme.css' in the entry module.

Installing

Every component ships in one package, @c2n/components, which also brings in @c2n/theme. Each component is its own entry (@c2n/components/table), and a package with several modules also exposes each one (@c2n/components/chart/line-chart), so an application loads only what it imports.

npm install @c2n/components
import '@c2n/components/theme.css'
import '@c2n/components/table' // one entry per component: registers c2-table and c2-table-column
import '@c2n/components/select'

import '@c2n/components' registers every component at once. That suits a prototype or an internal tool, but no bundler can tree-shake a registration away, so an application you ship should import the entries it uses. The icon sets are not included (Phosphor alone has 1,512 elements); install @c2n/feather-icons, @c2n/phosphor-icons or @c2n/symbols next to it. Optional engines such as uplot, echarts, CodeMirror and mermaid (the diagrams in c2-mermaid and c2-markdown) are optional peer dependencies: install the ones you use.

Attributes and properties

Two conversions differ from stock Lit, and both exist so that one binding is correct on the server and on the client.

A boolean attribute written as "false" is false. Lit’s default converter is presence-based — any attribute that is there at all is true — so disabled="false" would enable the flag. Every c2n boolean reads the literal strings "false" and "0" as false instead; a bare disabled, or disabled="", is still true. This matters wherever a framework renders an attribute by stringifying its value, which Svelte 5 does for every name outside the HTML boolean list and every server renderer does for all of them.

It also gives c2-table-column a genuine third state: sortable and resizable inherit the table’s setting when the attribute is absent, and sortable="false" is how one column opts out.

An array or object property accepts a JSON string. rows, columns, items, options, series and steps parse JSON out of their attribute and out of a string assigned straight to the property, so JSON.stringify(rows) is one binding that works in both directions — the server writes it as an attribute, the client sets it as a property, both parse. Pass the array itself wherever the framework can write a property. A string that is not valid JSON is left alone rather than silently becoming undefined.

The ;-separated multi-value properties behave the same way: value="a;b" and element.value = 'a;b' both yield ['a', 'b'] on c2-list, c2-select, c2-tree and c2-virtual-list.

A camelCase property is not a camelCase attribute. Lit derives the attribute by lowercasing the property, so readOnly is the attribute readonly and maxLength is maxlength; where a component renamed one it is kebab-case, like row-key. The API tables and the manifests list the real attribute name, with the property spelling alongside it under fieldName. A lookalike such as rowkey is forwarded to the real attribute with a console warning rather than dropped.

Typed events

Every component that fires events declares an event map, so addEventListener narrows the detail with no cast:

import type { Table } from '@c2n/components/table'

const table = document.querySelector('c2-table') as Table

table.addEventListener('selection-change', (event) => {
  event.detail.rows // TableRow[] — no cast
})

The map is per element rather than global, because the DOM’s single HTMLElementEventMap cannot describe two components that fire the same name with different details: selection-change carries row keys on c2-table and option values on c2-list.

For code that is generic over elements — a React hook, a helper that awaits an event — EventMapOf reads a component’s map back out of its type:

import type { EventMapOf } from '@c2n/core/event-helper.js'

function on<T extends HTMLElement, Type extends keyof EventMapOf<T> & string>(element: T, type: Type, listener: (event: EventMapOf<T>[Type]) => void) {
  element.addEventListener(type, listener as EventListener)
}

Selection events do not bubble. Five components fire selection-change, so a bubbling one would reach a listener meant for whatever the component is nested in — a select inside a tab panel would look like a tab switch. Put the listener on the element itself.

React

No wrapper. @c2n/components/react declares the tags in JSX; the props are derived from the element class, so a renamed property is a build error rather than a silent no-op.

// src/c2-elements.d.ts — every c2-* tag
import '@c2n/components/react'

One line declares every tag; nothing is registered by it, so it costs no bundle size. A component that also ships React hooks exports them from the same entry: import { useRenderedRows } from '@c2n/components/react' is the hook and the JSX types in one import.

// src/main.tsx — registration must happen before the first render
import '@c2n/components/theme.css'
import '@c2n/components/table'

React 19 assigns a prop as a property when the element has one, so rows={rows} lands as an object. That holds on the client only. On a server-rendered page — Next.js, React Router in SSR mode — React writes a custom element’s props into the HTML verbatim, the parser lowercases them, and hydration does not set properties: minWidth="240" reaches the element as minwidth, an attribute it does not declare. Write the kebab-case attribute name instead, min-width, expand-full, storage-key; the generated types list it next to the property. The component forwards the lowercase lookalike to the real attribute and warns in the console, so the value survives, but the warning is telling you to fix the spelling. For a converter-backed array or object, pass its JSON string during SSR; the generated React declarations accept both the property value and this serialized attribute form.

// Server-rendered: attributes by their markup name, structured values as JSON
<c2-dash-card col="1" row="1" min-width="240" expand-full>
<c2-table rows={JSON.stringify(rows)} columns={JSON.stringify(columns)} row-key="id" />

Next.js applies basePath to next/link, not to an href on a custom element. Resolve internal c2n links through the same small helper used for assets and plain anchors; keep absolute, hash, protocol, and already-prefixed URLs unchanged:

const basePath = process.env.NEXT_PUBLIC_BASE_PATH?.replace(/\\/$/, '') ?? ''

export function internalHref(href: string) {
  if (!href.startsWith('/') || href.startsWith('//') || !basePath || href === basePath || href.startsWith(`${basePath}/`)) return href
  return `${basePath}${href}`
}
<c2-link-button href={internalHref('/logs/')}>View logs</c2-link-button>

Events are the exception on the client too: JSX has no spelling for onSelection-change, and React’s onChange is its own synthetic form-control event, so custom events — and input/change — go through a ref:

const tableRef = useRef<Table>(null)

useEffect(() => {
  const table = tableRef.current
  if (!table) return
  const listener = (event: TableEventMap['selection-change']) => setSelected(event.detail.value)
  table.addEventListener('selection-change', listener)
  return () => table.removeEventListener('selection-change', listener)
}, [])

Many instances without server rendering

Declarative shadow DOM cannot share a stylesheet, so a server renderer copies a component’s whole stylesheet into every instance it renders. For a handful of elements that is fine; for a tree with 138 rows it took one page of this site from 606 KB to 2.2 MB of HTML. Repeated elements — tree items, list rows, navigation entries — should reach the browser as plain tags and upgrade there once the package is registered.

Build those tags as a string with a small helper of your own. It needs no dependency and touches no DOM, so it runs in any server runtime:

// lib/raw-element.ts
type AttributeValue = string | number | boolean | null | undefined | object

export const escapeHtml = (text: string) => text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')

/** `<tag …>innerHtml</tag>`. `innerHtml` is inserted as is: escape text with `escapeHtml`. */
export function rawElement(tag: string, attributes: Record<string, AttributeValue> = {}, innerHtml = ''): string {
  if (!/^[a-z][a-z0-9._]*-[a-z0-9._-]*$/.test(tag)) throw new TypeError(`Invalid tag name: ${tag}`)
  let html = `<${tag}`
  for (const [name, value] of Object.entries(attributes)) {
    if (value === false || value === null || value === undefined) continue
    if (!/^[^\\s"'>/=\\x00-\\x1f\\x7f]+$/.test(name)) throw new TypeError(`Invalid attribute name: ${name}`)
    // An object or array becomes JSON, which the JSON attributes (`rows`, `items`) parse back.
    html += value === true ? ` ${name}` : ` ${name}="${escapeHtml(typeof value === 'object' ? JSON.stringify(value) : String(value))}"`
  }
  return `${html}>${innerHtml}</${tag}>`
}

true writes a bare attribute; false, null and undefined omit it; an object or array is written as JSON (what JSON attributes such as a table’s rows or a tree’s items parse) and anything else as its escaped string. A tag or attribute name that would break out of the tag throws. Build the inner HTML from nested rawElement calls and pass text through escapeHtml.

Nothing sets properties on these elements before they upgrade, so write each attribute by its markup name (has-children, row-key). Keep content a crawler should see, such as links, in the light DOM, and hide the tags until they are defined (c2-tree:not(:defined) { visibility: hidden }).

Astro. With @astrojs/lit, a plain tag is still server-rendered once anything on the server imports its package, so pass the string to set:html and register the element in a client script:

---
import { escapeHtml, rawElement } from '../lib/raw-element'

const rows = files.map((file) => rawElement('c2-tree-item', { value: file.id }, `<a slot="label" href="${escapeHtml(file.url)}">${escapeHtml(file.name)}</a>`)).join('')
---

<Fragment set:html={rawElement('c2-tree', { 'aria-label': 'Files' }, rows)} />

<script>
  import '@c2n/components/tree'
</script>

Next.js and React Server Components. Build the string in the server component and inject it with dangerouslySetInnerHTML on a wrapper element. React does not reconcile the wrapper’s children, so the elements upgrading on the client cause no hydration mismatch. Register the package in a client component:

// app/files/page.tsx — a server component
import { escapeHtml, rawElement } from '@/lib/raw-element'
import { RegisterTree } from './register-tree'

export default async function FilesPage() {
  const files = await getFiles()
  const rows = files.map((file) => rawElement('c2-tree-item', { value: file.id }, escapeHtml(file.name))).join('')
  return (
    <>
      <RegisterTree />
      <div dangerouslySetInnerHTML={{ __html: rawElement('c2-tree', { 'aria-label': 'Files' }, rows) }} />
    </>
  )
}
// app/files/register-tree.tsx
'use client'
import { useEffect } from 'react'

export function RegisterTree() {
  useEffect(() => void import('@c2n/components/tree'), [])
  return null
}

Listen for their events from a client component on the element itself, found through a ref on the wrapper: selection-change does not bubble, so a listener on the wrapper never sees it.

Rendering a table cell with the framework

renderCell is handed to Lit, so it cannot return JSX, an Angular template or a Vue node — only a string, a DOM node, or a Lit template. For anything the framework should own, mark the column cell-slot: the table puts a <slot name="cell:<line>:<field>"> in each cell of that column, line being the cell’s display line (after sort, filter and grouping, page offset included), and the app renders one child per line the table reports through range-change into its light DOM.

@c2n/components/table ships that subscription as useRenderedRows: a React hook in @c2n/components/react and a Vue 3 composable in @c2n/components/vue, the same entries that declare the tags’ JSX and template types, so one import gives both. React and Vue are optional peer dependencies and @c2n/components/table itself never imports them, so only the app that imports the binding needs its framework.

import { useRenderedRows } from '@c2n/components/react'

const tableRef = useRef<Table>(null)
const rendered = useRenderedRows(tableRef) // TableRenderedRow[]

<c2-table ref={tableRef} rows={rows} rowKey="symbol">
  <c2-table-column field="change" header="Day" cellSlot />
  {rendered.map(({ line, row }) => (
    <span key={`${line}:change`} slot={`cell:${line}:change`} className={Number(row.change) >= 0 ? 'up' : 'down'}>
      {formatDelta(Number(row.change))}
    </span>
  ))}
</c2-table>

detail.rows holds one { line, key, row } entry per rendered data row, in display order. After a sort, filter or page change the same line holds a different row, and range-change fires again with the new rows — re-render from it rather than from your own rows.

The hook seeds itself from table.renderedRange (the table may have rendered before the effect ran), re-subscribes when the ref holds another element after a commit (a table inside a tab or a conditional), and runs nothing during render: on the server, during hydration and until the element upgrades it returns [], so every cell shows the column’s own formatting. The Vue composable takes a template ref, any ref or a getter and returns a Ref<TableRenderedRow[]> with the same rules, cleaned up when the component unmounts:

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useRenderedRows } from '@c2n/components/vue'

const rendered = useRenderedRows(useTemplateRef<HTMLElementTagNameMap['c2-table']>('table'))
</script>

<template>
  <c2-table ref="table" :rows.prop="rows" row-key="symbol">
    <c2-table-column field="change" header="Day" cell-slot></c2-table-column>
    <span v-for="{ line, key, row } in rendered" :key="key" :slot="`cell:${line}:change`">{{ formatDelta(Number(row.change)) }}</span>
  </c2-table>
</template>

Svelte, Angular or plain DOM code wraps the same core: subscribeRenderedRows(table, (rows) => …) from @c2n/components/table reports the rows already rendered, then every range-change, and returns the function that unsubscribes.

If the cell is better built as a Lit template, take html from @c2n/core/lit-helper.js rather than adding lit to the application — it is the same instance the components render with, so there is no version to pin by hand and no second copy of Lit in the bundle.

The children stay in the document’s light DOM, so ordinary CSS reaches them — a renderCell node lands inside the shadow root, where a stylesheet cannot, and has to be coloured through an inherited custom property. Only the lines the virtualizer has rendered have a slot, and range-change names exactly those, so the app renders a window’s worth of children however long the table is; a child whose line is outside the window is left unassigned. renderCell, or the column’s own formatting, stays as the slot’s fallback.

For a static export, put a semantic server-rendered table in slot="fallback". It is normal light DOM before the custom element upgrades, then the interactive grid takes over while the fallback stays in the DOM but is no longer rendered. This avoids an empty no-JavaScript result without leaving duplicate tables after hydration.

<c2-table ref={tableRef} row-key="id">
  <table slot="fallback" aria-label="Services">
    <tbody>{rows.map((row) => <tr key={row.id}><td>{row.name}</td></tr>)}</tbody>
  </table>
</c2-table>

Vue

One required piece of configuration, or every c2-* tag is resolved as a Vue component and renders nothing:

// vite.config.ts
vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('c2-') } } })

@c2n/components/vue registers every tag with Volar so templates can be checked strictly, and @c2n/framework-types/tsconfig.vue.json carries the compiler options that go with them:

{
  "extends": ["./tsconfig.base.json", "@c2n/framework-types/tsconfig.vue.json"]
}

Declare no vueCompilerOptions of your own alongside it — a local one replaces the inherited object rather than merging with it. What the fragment turns on:

  • strictTemplates, which is what makes the generated types do anything at all;
  • experimentalModelPropName, so v-model on a c2 control binds value (or checked on the toggles) instead of the modelValue Volar assumes for a component.

Vue picks between a property and an attribute with key in el, so registering the elements before mount() is what makes plain bindings land as properties. Two escape hatches, both worth knowing:

  • .prop forces a DOM property — :value.prop="[activeId]" for an array or object value;
  • .attr forces an attribute, needed when a component does not reflect a property but styles it with :host([attr]) — c2-chat-message’s align is that case, and is silently inert without it.

Kebab-case events bind by their real name (@selection-change), and the handler is typed from the component’s event map.

Angular

CUSTOM_ELEMENTS_SCHEMA on the component is the only configuration:

@Component({
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  imports: [FormsModule, ...C2_FORM_ACCESSORS],
  template: `<c2-table [rows]="orders()" [rowKey]="'id'" (row-click)="handleRowClick($event)"></c2-table>`,
})

[rows]="…" writes a property through setProperty, and (selection-change) / (row-click) bind kebab-case events by their real name. The trap is the reverse of React’s: a static attribute in a template stays an attribute, so rowKey="id" sets rowkey, which the component does not declare — use [rowKey]="'id'" or the real attribute name, row-key. The component forwards one of those lookalikes to the real attribute and warns in the console, instead of dropping the value in silence.

Angular’s built-in value accessors match input, select and textarea only, so ngModel or formControlName on a form-associated custom element finds no accessor, however correct its ElementInternals are. Angular then throws NG01203: No value accessor for form control in development, and fails on writeValue in a production build. ngDefaultControl borrows the <input> accessor, which never reads a toggle’s checked and resets an array value to ''. @c2n/angular supplies the missing half:

npm install @c2n/angular
import { C2_FORM_ACCESSORS } from '@c2n/angular'
// then in the template
// <c2-text-field [(ngModel)]="query" name="query"></c2-text-field>
// <c2-switch [(ngModel)]="openOnly" name="openOnly"></c2-switch>

Because the schema turns off type checking for these tags, $event is a bare Event in the template. Take the component’s event-map type in the handler and cast once there, rather than $any at every call site.

Svelte 5 and SvelteKit

Register the elements at module scope before anything renders — the root +layout.svelte script, or a src/lib/c2n.ts it imports. Svelte decides between a property and an attribute with key in element, so a binding on a tag that has not upgraded yet quietly falls back to an attribute.

<script lang="ts">
  import '@c2n/components/table'
  import type { TableEventMap } from '@c2n/components/table'

  let { orders } = $props()
  let selected = $state<string[]>([])
</script>

<c2-table
  row-key="id"
  rows={orders}
  sortable
  onselection-change={(event: TableEventMap['selection-change']) => (selected = event.detail.rows.map((row) => row.id))}
>
  <c2-table-column field="customer" header="Customer"></c2-table-column>
</c2-table>

Events take the on prefix and the event’s real name, with no colon: onclick, oninput, onselection-change. Svelte 4’s on:selection-change still compiles but is deprecated. selection-change does not bubble, so the handler belongs on the element itself.

A route that server-renders its data hits both conversions above. Svelte writes running="false" rather than omitting the attribute, which now reads as false; and String(rows) is [object Object], so pass rows={JSON.stringify(orders)} — the server writes it as an attribute and the client sets the same string as a property, and both parse. Spell camelCase properties as the attribute the component declares (row-key, not rowKey). Write numbers in braces: for an attribute that is also a property (precision, position), Svelte assigns the property, and precision="0" would hand it the string "0", which a Number property does not convert; precision={0} passes the number.

bind:value applies to form elements and Svelte components, not to a custom element, so bind by hand. Every c2n form control re-emits the inner control’s native input and change, which is what makes that one line enough:

<c2-text-field value={draft} oninput={(event) => (draft = event.currentTarget.value)}></c2-text-field>

In SvelteKit, register the elements from the client init hook rather than a layout script: src/hooks.client.ts exports init, which SvelteKit awaits before it hydrates, so every element is defined by the time the first binding runs, and the server never imports the components. A function property such as renderItem or rowPart must not reach the server at all (it would stringify its source into an attribute), so set it from an attachment, which runs only in the browser. For an array property whose converter does not parse a string assigned as a property, return JSON on the server and the value in the browser (browser ? rows : JSON.stringify(rows)). The Month-End Close Workbench prerenders every route this way:

// src/hooks.client.ts
export const init = async () => {
  await import('#lib/c2n.ts') // the side-effect imports of every element the app renders
}
<c2-kanban editable items={data(items)} oncard-move={onCardMove} {@attach (board) => (board.renderItem = renderCard)}>

There is no JSX-style declaration file to maintain, because Svelte accepts unknown elements. Point the editor at the generated html-custom-data.json below for attribute completion, and take event detail types from each package’s event map.

Editor support for templates TypeScript never sees

Plain .html, Angular templates and Vue SFCs get completion and documentation from two generated metadata files:

  • VS Code / Volar — point html.customData at ./node_modules/@c2n/framework-types/dist/html-custom-data.json;
  • WebStorm and the JetBrains IDEs — nothing to configure: @c2n/framework-types declares web-types in its package.json.

Both are generated from the same custom-elements manifests as the API tables on this site, so they cannot drift from the components.

Lit and plain HTML

Import what you render at the top of the module and use the tags. Extend a component for a variant (class AppField extends TextField), re-emit child events with redispatchEvent from @c2n/core/dom-helper.js, and set child variables in static styles on :host or on a class.

<script type="module">
  import '@c2n/components/theme.css'
  import '@c2n/components/button'
</script>
<c2-button>Save</c2-button>

“Lit is in dev mode”

A dev server — Next.js, Vite, the Angular CLI — logs Lit is in dev mode. Not recommended for production! once per page. That is expected: Lit publishes a development export condition with extra checks and clearer warnings, and dev servers resolve it. A production build resolves the default condition and the message is gone. There is nothing to configure, and silencing it in development would also silence the warnings it exists for.

Duplicate registration

customElements.define throws when a tag is already taken, which takes down the page. A second definition of a c2 element warns and keeps the first instead — so two versions of a package in one dependency tree, two micro-frontends, or a hot reload do not break the app. Fix the duplicate when you see the warning; the two copies are still two different classes.

AccordionConnected, animated panels with shared borders and single or multiple expansion.LayoutAction BarA floating bar for the current selection: the count, the bulk actions and a button that clears it.LayoutAddress fieldA postal address input with the parts, order and labels of each country, optional autocomplete from your address provider.InputsApp ShellThe frame of an application: header, navigation, main, an optional details panel, banner and footer, laid out from its own width.LayoutArea chartA line chart with the region under each line filled — every line-chart attribute, plus a fill opacity.ChartAttachmentFile and image attachments with metadata, upload progress, failure states, and actions.Data displayAudio PlayerWaveform playback for voice notes and recordings: the waveform is the seek bar, with the time, a speed toggle and an optional transcript.Data displayAudio VisualizerLive audio level as bars, a waveform or an orb: the mic meter of a call, a voice message, a voice assistant.Data displayAutocompleteSearchable combobox for local or remote items with customizable list rows.InputsAvatarImage, initials or icon for a person, with status dot and badge.Data displayBackdropA container that draws a decorative pattern behind its content: dots, a grid, stripes, flicker, aurora or meteors.LayoutBadgeTinted pill for status text, counts and dots, optionally pinned to a corner of another element.Data displayBannerA persistent, full-width message in the page flow for system notices, outages and account status.FeedbackBar chartBars over categories or time buckets, with several series drawn side by side within each band.ChartBar ListA ranked list whose rows carry their own bar: top pages, referrers, countries, or the changes between two periods.Data displayBarcodeA 1D or 2D barcode drawn as SVG in the browser: Code 128, EAN-13, EAN-8, UPC-A, Code 39 and Data Matrix.Data displayBorder BeamA decorative beam that travels around the border of any positioned container.LayoutBottom NavThe mobile tab bar: three to five destinations along the bottom of the screen, each an icon over a short label with a count or dot badge.NavigationBox plot chartThe distribution of each category as its median, quartiles, whiskers and outliers: latency per service, test scores, A/B results.ChartBreadcrumbNavigation trail of link buttons with separators, a current page and optional collapsing.NavigationBubble chartAn ECharts scatter plot with a third measure encoded as bubble area, for comparing how two measures relate while showing how much each point weighs.ChartBullet chartOne measure against a target and qualitative bands, a row per KPI, as Stephen Few drew it for scorecards.ChartButterfly chartTwo series back to back on a shared category axis, the categories named between them. The population pyramid is the classic case.ChartButtonThemeable button with slots for text, prefix, suffix and running icons.ButtonsButton GroupJoined actions and polished segmented controls with single or multiple selection.ButtonsCandlestick chartAn ECharts OHLC chart for market sessions and other open-close ranges, with semantic positive and negative colours.ChartCardGroups related content and actions on a single bordered surface.LayoutCard InputCard number, expiry date and security code in one field, formatted as the brand prints them and checked as they are typed.InputsCarouselA swipeable slideshow with previous/next controls, indicators, looping and optional autoplay.LayoutCascaderSelect a value from related, multi-level data in one floating panel.InputsCelebrationConfetti, a burst of sparks or rising emoji for a moment worth marking, fired from code or a click.FeedbackChange ReviewReview proposed edits hunk by hunk: accept or reject each change, then take the resulting text.Editors & viewersChat InputAuto-growing message composer with keyboard submission, toolbar actions and native form support.ChatChat MessageFlexible message row for conversations, assistant answers and activity updates.ChatChat Message ListScrolling conversation log that follows new messages, counts unread ones and loads older history.ChatCheckboxNative checkbox behaviour in a quiet, themeable box with an opt-in hover layer.InputsCheckbox groupCheckboxes grouped into one form control with a value array, a group label and an at-least-one rule.InputsChipA compact pill for a filter, a choice or an entered value: selectable as a toggle, removable with a button or the keyboard.InputsChip GroupA single row of chips or badges that collapses whatever does not fit into a +N indicator, recomputed as the row or its items change size.Data displayChoice MatrixRows × columns of checkboxes or radio buttons in one form control: permissions, notification settings, agreement scales.InputsChord chartFlows between groups around a circle, where one row is one flow from a source to a target.ChartCitationA numbered inline marker that names its source, with a hover preview of the page it cites.ChatCode EditorEditable, syntax-highlighted source field on CodeMirror 6, themed entirely through CSS variables.Editors & viewersCode ViewerSyntax-highlighted code with line numbers, copy button and dark mode, powered by shiki.Editors & viewersColor AreaTwo-dimensional area for picking saturation and value of a colour.InputsColor SelectColour swatch that opens a full picker built from area and slider.InputsColor SliderHorizontal slider for choosing a hue from 0 to 360.InputsCommandA searchable list of commands: type to filter, arrows to highlight, Enter to run. The core of a ⌘K palette.NavigationComment ThreadOne conversation on a piece of content: the first comment and its replies, a reply box, edit and delete on your own comments, and Resolve.ChatComparison BarSegmented ratio bar comparing two or three quantities, such as the bid/ask balance of an order book.Data displayConfirm DialogAsks the user to confirm an action: await show() or confirm() for a true/false answer, with a destructive variant and an async confirm action.FeedbackContext MenuA menu opened by right-clicking or long-pressing an area, built from a slotted c2-menu or a render function per clicked spot.NavigationCopy ButtonButton that copies text to the clipboard — the element it sits in, another element by id, or a literal string.ButtonsCountdownTime left until a date, or through a timer, drawn as segments, a clock or a phrase, and kept current to the second.Data displayDashboardGrid of resizable panes, dragged by the edges they share.LayoutDate InputForm-associated single-date input with native picker, constraints, helper text and error states.InputsDate SelectorAccessible one- or two-month calendar for choosing a date range.InputsDate StripA row of days to pick one from, a week at a time, with dots on the days that have something on them.InputsDate Time PickerForm-associated date-and-time field with a calendar and time popup, min/max/step constraints and an IANA time zone.InputsDepth chartCumulative bid and ask size around the mid price of an order book, as trading screens show market depth.ChartDescription ListRead-only label and value pairs for a detail page, in columns that wrap with the available width.Data displayDetailsCollapsible disclosure built on native details and summary.LayoutDiff ViewerRead-only line diff of two texts, unified or side by side, with folded unchanged lines and word highlights.Editors & viewersDistribution BarOne 100% stacked bar of a breakdown, with a legend of shares and the hovered part brought forward.Data displayDuration InputA text field for an amount of time: reads 1h 30m, 90m, 1.5h or 1:30 and stores seconds.InputsEmoji PickerA searchable grid of emoji in categories, with skin tones and the emoji used last.InputsFacet ListOne facet of a filter sidebar: an attribute’s values with counts, to include or exclude from a search.InputsFieldsetGroups related controls under a legend, with a description, a group-level error and a disabled state that reaches every control inside.InputsFile browserFolders and files with a folder tree, breadcrumbs, search, list and grid views, selection, a context menu, renaming and drag and drop.Data displayFilter BuilderA filter bar of sentence chips (Status is any of Active), an add-filter picker and an editor for each part; it reports a plain JSON filter tree.InputsFloat ButtonA floating action button for the main action of a screen, which can expand into a speed dial of related actions.ButtonsFloating PanelA window inside the page that the user drags, resizes, minimises and maximises, without blocking the page.LayoutFlowA pipeline, dependency graph or database schema on a pannable canvas: steps with a status, smooth edges, rows joined row to row, auto layout, draggable nodes saved to localStorage, a hover card and a context menu.Editors & viewersFormForm container with validation timing, an error summary, server errors, dirty tracking and async submit.InputsForm FieldLabel, hint, error, warning and character counter around any form control, wired for assistive technology.InputsFormat DateA date and time formatted for the reader's language and, optionally, a fixed time zone.Data displayFormat NumberA number formatted for its locale: currency, percent, units, compact counts and byte sizes, from Intl.NumberFormat.Data displayFX ConverterTwo linked amount and currency fields: type what you send or what arrives, with the fee, a swap and a guaranteed rate.InputsGanttA read-only project timeline: tasks, groups, milestones and dependencies on a day, week or month scale.PlanningGauge chartA focused radial KPI gauge drawn by ECharts, with a configurable scale, progress arc and pointer.ChartGoogle MapGoogle Maps with markers, a road route from A to B, and Street View, loaded once per page from a browser key.Data displayHeaderArranges brand, navigation, actions and a mobile trigger in a reusable site shell.LayoutHeatmap chartRows binned into cells by two keys, aggregated, and coloured by a sequential, diverging or stepped scale: a currency matrix, pair × tenor, hour × weekday, or a calendar of days. Drawn by ECharts, with a colour key that counts, marks and filters.ChartHighlightMarks the parts of a text that match a search query.Data displayHover CardInteractive preview card shown when a link or mention is hovered or focused, rendered in the top layer.FeedbackIcon ButtonRound, hoverable button wrapping a slotted SVG icon.ButtonsImageImage with lazy loading, a loading placeholder, an error fallback, click-to-load for untrusted URLs and a full-screen preview.Data displayImage annotatorArrows, boxes, text, numbered steps, blur and redaction over an image, exported as a PNG and as shapes.Editors & viewersImage CompareA before/after slider: two layers on top of each other, the after layer cut at a divider the reader drags.Data displayImage CropperChoose the part of an image to keep by moving and resizing a selection, then read it back as a canvas or a blob.Editors & viewersIn ViewReacts when its content scrolls into view: reveal it, load more, or pause it when it leaves.LayoutIndicatorSolid dot or count pinned to an edge or corner of any element, with an accessible name for the count.Data displayInline EditText that turns into a field on click: Enter (Ctrl/⌘+Enter when multiline) commits, Escape cancels, and any control with a value that fires change can be the editor.InputsJSON FormA form generated from a JSON Schema, laid out by the schema, a UI schema or your own markup.InputsJSON ViewerBrowse a JSON value as a collapsible tree, search it, and copy any path or value.Editors & viewersKanbanBoard layout and card movement by pointer or keyboard, with cards drawn by your own renderItem.PlanningKbdKeyboard key label for shortcuts and command hints, with the semantics of the native kbd element.Data displayKey Value EditorEditable KEY=value rows for environment variables and headers: paste a .env file to split it into rows, mask secret values, and flag duplicate or malformed keys.InputsLabelCaption that names and activates the control referenced by its for attribute, with a required marker.InputsLine chartA line chart over a time or numeric x axis, drawn on canvas by uPlot, with series declared as children.ChartLink ButtonText-styled control for link and navigation actions, rendered as a real anchor or a button.NavigationListVertical list container with single or multiple selection.Data displayList ItemSelectable row with icon slots, used on its own or as the option of list and select.Data displayLoading OverlayCovers a region while it loads: the content stays in place, dimmed and blocked, with a spinner and an announced label.FeedbackLog ViewerExplore large logs with variable-height virtual scrolling and sticky tabular attributes.Editors & viewersMap chartA choropleth and point map on vector outlines, drawn by ECharts: regions shaded by a value, with layers of sized points and flow lines, on the bundled world and US maps or your own GeoJSON, TopoJSON or SVG.ChartMarkdownSafe markdown renderer that streams: chat answers and READMEs, with code, math and diagrams.ChatMarkerAn inline marker that highlights, underlines, strikes through, boxes or circles a run of text.Data displayMask InputA text input that fits what is typed to a pattern, for postcodes, IBANs, tax IDs, expiry dates and other fixed formats.InputsMasonryAutomatically pack differently sized dashboard tiles, then move or resize them in edit mode.LayoutMaster DetailA list beside the record it opens, one pane at a time with a Back step when the element is narrow.LayoutMathTeX formulas rendered as native MathML: no font CSS, readable by screen readers, safe for untrusted input.Data displayMenuCommands, links, checkboxes and submenus in a popover anchored to a trigger.NavigationMenubarDesktop-style File, Edit and View menus in one bar, with the arrow-key and hover movement of an application menu bar.NavigationMermaidMermaid text diagrams drawn in your theme colours, loaded on demand and safe for untrusted sources.Data displayMeterA measurement inside a known range — storage, quota, strength — with native meter thresholds and an optional segmented breakdown.Data displayMind MapAn idea tree laid out around a central topic, with keyboard editing as in XMind and Miro.Editors & viewersModalDialog built on the native dialog element: focus trap, backdrop, Escape, title, body and footer.FeedbackMoney InputCurrency amount field that groups digits as you type, follows the locale and stores an integer count of minor units.InputsMonth PickerPick a month, or a year: billing periods, reports and card expiry.InputsMonth PlannerMonth calendar that draws events and trips as bars across the days they cover.PlanningNavigation MenuSite navigation bar whose triggers open panels of links below the header.NavigationNotepadA rich-text notepad that looks like paper: handwriting on ruled lines, a formatting toolbar over the selection, checklists and tear-off pages.PlanningNotification CenterAn inbox panel of notifications grouped by day, with read and unread states and mark all as read.FeedbackNumber InputForm-associated numeric input with native validation, step controls, adornments and helper states.InputsOrder BookLive bids and asks with depth bars, price grouping and a spread row, for a trading screen.Data displayOrg ChartA hierarchy drawn as connected cards that collapse and expand: an organization chart, a reporting line or a team structure.Data displayOTP InputForm-associated one-time-code field with one cell per character, paste and SMS autofill, grouping and masking.InputsOverflow ListOne row of actions or links that shows what fits and folds the rest into a "More" menu, refitting as its width changes.LayoutOverlap chartA Venn diagram of two or three sets, where each circle is a set and each shared area counts the members of exactly that combination.ChartOverlayAnchored popup built on the browser Popover API, positioned with floating-ui.FeedbackPage EditorA Notion-style editor for long text: Markdown shortcuts while you type, a / menu for blocks, a toolbar over selected text, code blocks with syntax colours and eight named colours.Editors & viewersPage HeaderThe top of a page: breadcrumb or parent link, your heading with status badges, description, metadata, page actions that fold into a menu, tabs and a toolbar.LayoutPaginationPage navigation in three layouts: numbered pages, a simple page status, or a table-footer row with rows-per-page.NavigationPan ZoomA viewport that pans and zooms its content: an image, an SVG diagram, a floor plan or a board.Editors & viewersPassword FieldA password input with a show/hide toggle, a strength meter, a requirements checklist and a Caps Lock warning.InputsPayment CardA company card face that stays masked until the reader asks, then shows each detail with a copy button.Data displayPhone InputA phone number field with a searchable country picker, as-you-type grouping and an E.164 value.InputsPie chartA pie or donut chart drawn by ECharts, where one row is one slice and label-field names it.ChartPivot tableSummarises flat rows by the fields set on rows and columns, with subtotals, totals, sorting, a heatmap and a field list.Data displayPopconfirmA small "Are you sure?" popup anchored to the button that asked for it.FeedbackProgressProgress bar or ring, indeterminate or filling to a value, with an optional label and count.FeedbackPromo Code FieldApply a promo code with checking and refusal states; applied codes stay as removable tokens with their saving.InputsPyramid chartRanked levels of a whole, stacked from a narrow apex to a wide base, where one row is one level.ChartQR CodeGenerate accessible, themeable QR codes locally as crisp SVG graphics.Data displayQuantity PickerSeveral labelled counters behind one summary ("2 adults, 1 child"), with limits per counter and in total.InputsQuery InputA key:value query field whose filters are chips you can switch off or remove, with syntax highlighting and field and value suggestions, as in Datadog, GitHub and Sentry.InputsQuestionnaireA multi-step single- and multiple-choice flow with validation, shortcuts and form submission.InputsQuick time menuA menu of quick times (Later today, Tomorrow, Next week), each showing when it falls, plus a date and time picker for anything else.InputsRadar chartCompare several profiles across the same set of normalized indicators.ChartRadial bar chartConcentric rings, each filling toward its own goal: activity rings, a readiness score, a budget per category.ChartRadioRadio options built on native inputs, grouped into one value with keyboard navigation.InputsRateAccessible star rating input with hover preview, keyboard control and optional half values.InputsReaction BarEmoji reactions with counts under a message or comment, toggled by the current user, with a picker to add more.ChatRelative TimeTime relative to now, such as "3 minutes ago" or "in 2 days", that keeps itself current and shows the full date on hover.Data displayReorder ListReorder a vertical queue with pointer or keyboard input while the application owns persistence.Data displayRich Text EditorA compact formatted-text field for comments, replies and email bodies: bold, links, lists, quotes and code, a one-tab-stop toolbar, and a Markdown value.InputsSankey chartQuantities moving through stages, where one row is one flow and each band is as wide as what it carries.ChartSave BarA floating bar that appears while a form has unsaved changes, with Discard and Save.LayoutScatter chartAn ECharts scatter plot for finding relationships, clusters and outliers across two numeric measures.ChartSchedulerResource timeline: people, rooms or machines down the side, time across the top, each booking a bar you can drag.PlanningScroll AreaA scroll container with themed scrollbars and shadows on the edges with more content beyond them.LayoutScroll ProgressHow far the reader got: a bar that fills or a ring that closes as the page or a container scrolls.FeedbackSearch FieldA search box with a debounced search event, a clear button, a keyboard shortcut hint and recent searches.InputsSecret FieldShows an API key or other secret masked, with buttons to reveal it, copy it and ask for a new one.InputsSelectDropdown that pairs a themeable trigger with an anchored list of c2-list-item options.InputsSelection ToolbarA toolbar that appears over the text a reader selects: Ask AI, Comment, Bold, or any action the app slots in.Editors & viewersSeparatorHorizontal or vertical rule with an optional label, for dividing content and toolbars.LayoutShare SheetShare a link anywhere: copy it, send it to email or a network, scan it as a QR code, or hand it to the device’s own share sheet.FeedbackSheetDialog pinned to an edge of the screen, for content that complements the page rather than interrupting it.FeedbackShortcutKeyboard shortcuts for a whole application from one element: each binding fires an action event, runs a handler or activates a target.InputsSide NavResponsive navigation drawer beside the page: pushes the content on large screens, slides over it with a backdrop on small ones.NavigationSkeletonPlaceholder block standing in for content that has not arrived, in three shapes and three animations.FeedbackSkip LinkA "Skip to main content" link that appears when a keyboard user tabs onto it and moves focus into the main content.NavigationSliderRange input with a themeable track, thumb, step ticks and value bubble.InputsSparklineA chromeless trend line sized for a table cell, a list row or the trend slot of a c2-stat.ChartSpinnerCircular progress indicator, indeterminate or showing a value, with optional text.FeedbackSplit ButtonA main action with a caret menu of alternatives, such as "Send ▾", or a remembered choice like a merge method.ButtonsSplit PanelTwo panels separated by a divider the user drags, or moves with the keyboard, to resize them.LayoutSpreadsheetA small editable grid with basic formulas, for budgets, quotes, price lists and score sheets.Editors & viewersStatDisplays a KPI with an optional icon, trend and supporting description.Data displayState TimelineOne band per series, coloured by the discrete state it was in over time: service health, CI runs, host power.ChartStatus LightColoured dot plus a short label for the state of something, such as Live, Building or Failed.Data displayStatus PanelCommunicate empty states, operation outcomes and recoverable errors with clear next steps.FeedbackStepsThe trace of a task as it runs, or a stepper across the top of a view: statuses, durations, stages that open while they work, and steps the reader can select.Data displayStickyPins content to the top or bottom of its scroll container and tells you when it is stuck, optionally hiding it while the reader scrolls down.LayoutStreaming TextPlain text that arrives in bursts, such as an LLM answer, revealed at a steady pace behind a caret.ChatSunburst chartA hierarchy drawn as rings, the whole at the centre and each ring splitting its parent: budgets, disk usage and org spend, with click to zoom.ChartSwipe ActionA list row that slides sideways to reveal actions behind it, as in a mail or task list on a phone.LayoutSwitchOn/off toggle on a native switch input, with label, description and thumb icons.InputsTableVirtualized data grid with declarative columns, sorting, selection, pinning and resizing.Data displayTable Of ContentsAn "On this page" navigation that lists the headings of a document, nests them by level and highlights the section being read.NavigationTabsTab strip that shows one content panel at a time.NavigationTag InputTurns typed or pasted text into removable tags — recipients, keywords, labels — with configurable delimiters, parsing and validation.InputsTerminalAn interactive prompt for commands your app answers: history, Tab completion, streaming output, Ctrl+C, ANSI colours.Editors & viewersText EffectAnimated text for a hero line or a status: rotating words, a typewriter, a scramble, a shimmer or a staggered reveal.Data displayText FieldSingle-line input with icon slots, clear button, helper and error text, and a character counter.InputsTextareaMultiline text input with resizing, helper and error text, and a character counter.InputsTheme SelectColour-theme switcher: click to step to the next mode, hover for the full menu.InputsTime InputForm-associated time-of-day input with a scroll-wheel picker, step and range constraints, helper text and error states.InputsTime Range PickerDashboard time range control: searchable quick ranges, an absolute range typed or picked on a calendar, time zone, shift and zoom, and auto-refresh.InputsTime Slot PickerPick a free time from someone's calendar: a row of days, then that day's times grouped into morning, afternoon and evening.InputsTimelineA vertical sequence of dated events on a connected rail: order history, activity feeds, changelogs.Data displayToastNotification cards and a manager for stacked, queued notifications with independent lifetimes.FeedbackTodo ListA to-do list with the feel of paper: hand-drawn ticks, notes, highlighters, swipe and drag, and a remembered look.PlanningToggletipA small ⓘ button that opens a help bubble on click or tap; the bubble can hold links and is announced to screen readers.FeedbackTooltipContextual hint shown when its target is hovered or focused, rendered in the top layer.FeedbackTourA guided product tour that spotlights one element at a time and explains it in a panel beside it.FeedbackTrace WaterfallShow the spans of a distributed trace as a tree, each with a duration bar on one shared time axis.Data displayTranscriptSpeaker-labelled, timestamped segments synced to playback: click to seek, the word playing marked, search built in.Data displayTreeHierarchical tree view with expansion, selection, checkboxes and lazy loading.Data displayTree SelectA dropdown for picking one or several rows from a hierarchy, with checkboxes, search and a form value.InputsTreemap chartA whole split into nested rectangles, each sized by its value: market maps, budgets and disk usage.ChartTruncateClamps text to a number of lines, with an optional Show more button.Data displayUploadDrag-and-drop file selection with validation, upload progress, retry, cancellation, and attachment results.InputsVideo GridLays out the tiles of a call as a grid, a spotlight or a sidebar, every tile as large as the space allows.LayoutVideo PlayerThemed controls over a real video: chapters on the seek bar, a thumbnail while scrubbing, captions, speed, picture-in-picture and full screen.Data displayVideo TileOne participant of a call: their video or avatar, with their name, a live microphone meter and status badges.Data displayView OptionsThe Display popover of a collection: layout, grouping, ordering on several levels, Show switches and visible properties, reported as one plain value.InputsVirtual ListWindowed list with built-in search, sorting, selection and an async data source.Data displayVisually HiddenText for screen readers only: kept in the accessibility tree, drawn nowhere, optionally shown while it holds focus.LayoutWaterfall chartA running total bridged by the steps that move it, where one row is one step and totals mark where the bridge lands.ChartWeek PlannerWeekly schedule with time blocks: a typical week with optional odd and even weeks, or a dated week the user edits.PlanningWheel PickeriOS-style scroll wheels to pick a time, a height, a weight or any value from a short list, by swipe, scroll, click or keys.InputsWorking IndicatorA live "work in progress" line: animated mark, shimmering label, trailing dots and elapsed time.FeedbackFeather Icons287 open-source Feather icons, one web component each.IconsMat Icon2,234 Material Icons ligatures rendered through a single element.IconsPhosphor Icons1,512 flexible icons in six weights, one web component each.IconsSymbols157 themeable spot illustrations for empty states, status panels, onboarding and error pages.IconsTask icons191 duotone icons for to-do items: work, home, health, sport, learning, food, money, travel and more.IconsThemingTheme every c2n component from a handful of design tokens with @c2n/theme, or reach for any component variable directly.GuideLanguagesComponents show their text in English; add your app’s languages as one JSON object, picked by the nearest lang.GuideFrameworksUse the components from React, Vue, Angular, Svelte, Lit or plain HTML — typed events, generated template types, and the one piece of configuration each framework needs.GuideUsing c2n in an applicationThe workflow behind every c2n app: load the theme once, use c2-* tags directly, and turn every repeated pattern into a small variant or composed component.GuideAI toolsGive Claude Code, Codex, Google Antigravity or GitHub Copilot the c2n skill, live component APIs, examples and application conventions.GuideNo components match.