Using c2n in an application

The 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.

The goal is less code per screen. Three layers do the work: a theme that makes the plain tags look right, variant components for the looks you repeat, and composed components for the patterns you repeat. This docs site is built that way; the files named below are its reference implementation.

1. Load the theme once

Install @c2n/theme with the components you use, import it at the application root, and set the tokens that differ from the defaults. Every c2-* element then follows your brand without per-component CSS.

import '@c2n/theme/theme.css'
import '@c2n/button'
import '@c2n/text-field'
:root {
  --c2-theme--color-primary: #7c3aed;
  --c2-theme--radius-md: 10px;
  --c2-theme--font-family: 'Inter', system-ui, sans-serif;
}

If the application already has design tokens, load @c2n/theme/base.css alone and bridge your tokens onto the --c2-theme--* names, as apps/ui/src/assets/c2-theme.scss does for this site. Dark mode then follows your own switch. See Theming for the token list.

2. Use the tags directly

With the theme in place, a component that appears once needs nothing else:

<c2-button>Save</c2-button>
<c2-text-field placeholder="Email" type="email"></c2-text-field>

Attributes, slots and events are documented on each component’s API tab. Icons are components too: c2-feather-<name> from @c2n/feather-icons.

3. Variant components for repeated looks

The moment the same look appears twice, name it. A variant wraps one c2 component with a fixed set of CSS variables and attributes. Three shapes, from cheapest to richest; pick the first one that fits.

A class. The look differs, the markup does not. Component variables set on a class win over the theme because the theme only sets :root values.

.danger-button {
  --c2-button__container--background-color: var(--c2-theme--color-error);
  --c2-button__container__hover--background-color: color-mix(in srgb, var(--c2-theme--color-error), black 12%);
}

A wrapper component in your framework. Attributes and slots repeat as well: an icon button with a tooltip and a fixed size, a card that is always a link. This site’s SiteIconButton.astro renders c2-icon-button with aria-label, tooltip and a size class:

<!-- one call site -->
<SiteIconButton label="Copy code" size="sm"><c2-feather-copy></c2-feather-copy></SiteIconButton>

<!-- the wrapper: a class block on the c2 element, nothing else -->
<c2-icon-button class="site-icon-button site-icon-button--sm" aria-label="Copy code" tooltip="Copy code">…</c2-icon-button>
<style>
  .site-icon-button {
    --c2-icon-button--border-radius: var(--c2-theme--radius-sm);
    --c2-icon-button__hover--background-color: var(--c2-theme--color-surface-container);
  }
  .site-icon-button--sm {
    --c2-icon-button__state-layer--size: 30px;
    --c2-icon-button__icon--width: 16px;
    --c2-icon-button__icon--height: 16px;
  }
</style>

A Lit subclass. When the variant must exist as its own tag (used from strings, other Lit templates, or shipped as a package), extend the component and bake the variables in. The inspector’s Code tab generates this for you from any example:

import { css } from 'lit'
import { Button } from '@c2n/button'

export class DangerButton extends Button {
  static override styles = [
    Button.styles,
    css`
      :host {
        --c2-button__container--background-color: var(--c2-theme--color-error);
      }
    `,
  ]
}
customElements.define('app-danger-button', DangerButton)

Rules that hold for every shape: set child variables on the host element or a class, never through ::part (the components do not expose parts); register variants under your own prefix (app-*, site-*), never c2-*; one file per variant in one directory (src/components/ui/ here).

4. Composed components for repeated patterns

When several c2 components and some logic repeat, compose them into one component. The primitives keep their responsibilities; you add the glue. This site’s ⌘K palette (SearchPalette.astro) is c2-modal + c2-text-field + c2-list with c2-list-item rows:

  • the modal brings the focus trap, Escape, backdrop click, scroll lock and focus restore;
  • the text field brings the input, icons and clearing;
  • the list drives the highlighted row through its value;
  • the palette itself only filters rows and maps arrow keys and Enter.

Set the children’s variables on the composed component’s class (--c2-text-field--border-top: none on .search-palette__field), forward the attributes you want configurable, and re-emit child events you want callers to see.

5. Gaps stay in the application

When no c2 component fits (a keyboard hint, a count pill), write a plain application component styled with the same --c2-theme--* tokens so it follows the theme. Do not fork or patch a @c2n/* package; if the pattern is general, propose a component instead.

Reference implementation

  • apps/ui/src/assets/c2-theme.scss — the token bridge of this site.
  • apps/ui/src/components/ui/SiteIconButton, SiteButton, SiteCard, the icon barrel.
  • apps/ui/src/components/SearchPalette.astro — a composed component.
  • apps/ui/src/layouts/DocComponentLayout.astro — the mobile drawer on c2-side-nav.
escAccordionConnected, animated panels with shared borders and single or multiple expansion.LayoutAvatarImage, initials or icon for a person, with status dot and badge.Data displayBadgeTinted pill for status text, counts and dots, optionally pinned to a corner of another element.Data displayBreadcrumbNavigation trail of link buttons with separators, a current page and optional collapsing.NavigationButtonThemeable button with slots for text, prefix, suffix and running icons.ButtonsButton GroupAttached buttons with shared borders, optionally a segmented control with single or multiple selection.ButtonsCardGroups related content and actions on a single bordered surface.LayoutChat InputAuto-growing message box with a send button for chat interfaces.ChatChat MessageOne chat bubble with avatar, title, timestamp and message body.ChatCheckboxNative checkbox behaviour in a quiet, themeable box with an opt-in hover layer.InputsCode ViewerSyntax-highlighted code with line numbers, copy button and dark mode, powered by shiki.Data displayColor 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.InputsCopy ButtonButton that copies text to the clipboard — the element it sits in, another element by id, or a literal string.ButtonsDetailsCollapsible disclosure built on native details and summary.LayoutIcon ButtonRound, hoverable button wrapping a slotted SVG icon.ButtonsKbdKeyboard key label for shortcuts and command hints, with the semantics of the native kbd element.Data displayLabelCaption that names and activates the control referenced by its for attribute, with a required marker.InputsLink 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 displayMenuCommands, links, checkboxes and submenus in a popover anchored to a trigger.NavigationModalDialog built on the native dialog element: focus trap, backdrop, Escape, title, body and footer.FeedbackNavigation MenuSite navigation bar whose triggers open panels of links below the header.NavigationOverlayAnchored popup built on the browser Popover API, positioned with floating-ui.FeedbackPaginationPage navigation in three layouts: numbered pages, a simple page status, or a table-footer row with rows-per-page.NavigationProgressLinear progress bar, indeterminate or filling to a value, with an optional label and count.FeedbackRadioRadio options built on native inputs, grouped into one value with keyboard navigation.InputsSelectDropdown that pairs a themeable trigger with an anchored list of c2-list-item options.InputsSeperatorHorizontal or vertical rule with an optional label, for dividing content and toolbars.LayoutSheetDialog pinned to an edge of the screen, for content that complements the page rather than interrupting it.FeedbackSide 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.FeedbackSliderRange input with a themeable track, thumb, step ticks and value bubble.InputsSpinnerCircular progress indicator, indeterminate or showing a value, with optional text.FeedbackSwitchOn/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 displayTabsTab strip that shows one content panel at a time.NavigationText 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.InputsToastNotification cards and a manager for stacked, queued notifications with independent lifetimes.FeedbackTooltipContextual hint shown when its target is hovered or focused, rendered in the top layer.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.IconsThemingTheme every c2n component from a handful of design tokens with @c2n/theme, or reach for any component variable directly.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.Guide