@molecule/app-ui-tailwind

Provider bond · ui · App (browser) · v1.2.6 · Apache-2.0

Tailwind CSS UI components for molecule.dev

npm install @molecule/app-ui-tailwind

npm · Source on GitHub · Implements @molecule/app-ui

How it works

@molecule/app-ui-tailwind is a provider bond on the app (browser) side: it implements the ui core interface (@molecule/app-ui) with a concrete vendor or library behind it.

Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.

// 1. Wire the ClassMap once at startup (bonds.ts):
import { setClassMap } from '@molecule/app-ui'
import { classMap } from '@molecule/app-ui-tailwind'

setClassMap(classMap)

// 2. In the app's Tailwind v4 CSS entry (e.g. src/index.css):
//      @import 'tailwindcss';
//      @import '@molecule/app-ui-tailwind/base.css';   <- theme tokens
//      @source "../node_modules/@molecule/app-ui-tailwind/dist";  <- keep
//        Tailwind's purge from stripping the classes this package emits
// (Scaffolded molecule apps already have both — this is only needed when
//  integrating into an app not generated by mlcl.)

// 3. Resolve classes anywhere via the ClassMap — never hardcode Tailwind:
import { getClassMap } from '@molecule/app-ui'
const cm = getClassMap()
element.className = cm.button({ color: 'primary', size: 'md' })

Works with: @molecule/app-styling, @molecule/app-ui

Live demos

Running apps built with Molecule that install this package — open the app or study the template.

Reference

Tailwind CSS UI components for molecule.dev.

Provides pre-built, accessible UI components using Tailwind CSS classes. These are framework-agnostic class strings that can be used with any rendering library (React, Vue, Svelte, etc.).

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Quick Start

// 1. Wire the ClassMap once at startup (bonds.ts):
import { setClassMap } from '@molecule/app-ui'
import { classMap } from '@molecule/app-ui-tailwind'

setClassMap(classMap)

// 2. In the app's Tailwind v4 CSS entry (e.g. src/index.css):
//      @import 'tailwindcss';
//      @import '@molecule/app-ui-tailwind/base.css';   <- theme tokens
//      @source "../node_modules/@molecule/app-ui-tailwind/dist";  <- keep
//        Tailwind's purge from stripping the classes this package emits
// (Scaffolded molecule apps already have both — this is only needed when
//  integrating into an app not generated by mlcl.)

// 3. Resolve classes anywhere via the ClassMap — never hardcode Tailwind:
import { getClassMap } from '@molecule/app-ui'
const cm = getClassMap()
element.className = cm.button({ color: 'primary', size: 'md' })

Type

provider

Installation

npm install @molecule/app-ui-tailwind @molecule/app-styling @molecule/app-ui

API

Interfaces

CVAConfig

Configuration for a class-variance-authority (cva) function: the variant definitions, default selections, and compound variants used to resolve a component's final class string from its props.

interface CVAConfig<T extends Record<string, Record<string, string>>> {
  variants?: T
  defaultVariants?: {
    [K in keyof T]?: keyof T[K]
  }
  compoundVariants?: Array<
    {
      [K in keyof T]?: keyof T[K]
    } & {
      class: string
    }
  >
}

Types

ClassValue

Class name value types accepted by cn.

type ClassValue =
  | string
  | number
  | boolean
  | undefined
  | null
  | ClassValue[]
  | Record<string, boolean | undefined | null>

Constants

classMap

Tailwind CSS UIClassMap implementation mapping abstract UI tokens to Tailwind class strings.

const classMap: UIClassMap

button

Button component classes.

const button: (
  props?:
    | ({
        variant?:
          | 'default'
          | 'secondary'
          | 'success'
          | 'outline'
          | 'ghost'
          | 'link'
          | 'warning'
          | 'danger'
          | undefined
        size?: 'sm' | 'md' | 'lg' | 'icon' | undefined
      } & { class?: string })
    | undefined,
) => string

input

Input component classes.

const input: (
  props?:
    | ({
        variant?: 'default' | 'error' | undefined
        size?: 'sm' | 'md' | 'lg' | undefined
        align?: 'left' | 'center' | undefined
      } & { class?: string })
    | undefined,
) => string

textarea

Textarea component classes.

const textarea: (
  props?:
    | ({ variant?: 'default' | 'error' | undefined; size?: 'sm' | 'md' | 'lg' | undefined } & {
        class?: string
      })
    | undefined,
) => string

select

Select component classes.

const select: (
  props?:
    | ({ variant?: 'default' | 'error' | undefined; size?: 'sm' | 'md' | 'lg' | undefined } & {
        class?: string
      })
    | undefined,
) => string

checkbox

Checkbox component classes.

const checkbox: (
  props?: ({ variant?: 'default' | 'error' | undefined } & { class?: string }) | undefined,
) => string

radio

Radio component classes.

const radio: (
  props?: ({ variant?: 'default' | 'error' | undefined } & { class?: string }) | undefined,
) => string

switchBase

Switch component classes.

The color axis maps 1:1 to the semantic ColorVariant scale (same primary/secondary/success/warning/error/info tokens progressColor already uses) — no lookup table needed, unlike button's differently-named CVA variants.

const switchBase: (
  props?:
    | ({
        color?: 'secondary' | 'success' | 'warning' | 'error' | 'primary' | 'info' | undefined
        size?: 'sm' | 'md' | 'lg' | undefined
      } & { class?: string })
    | undefined,
) => string

switchThumb

Switch thumb (the sliding circle) classes.

const switchThumb: (
  props?: ({ size?: 'sm' | 'md' | 'lg' | undefined } & { class?: string }) | undefined,
) => string

label

Label component classes.

const label: (
  props?: ({ required?: 'true' | 'false' | undefined } & { class?: string }) | undefined,
) => string

formError

Form error message classes.

const formError: 'text-sm text-error mt-1'

formHint

Form hint message classes.

const formHint: 'text-sm text-foreground-secondary mt-1'

card

Card component classes.

const card: (
  props?:
    | ({ variant?: 'default' | 'outline' | 'ghost' | 'elevated' | undefined } & { class?: string })
    | undefined,
) => string

cardHeader

The card header.

const cardHeader: 'flex flex-col space-y-1.5 p-6'

cardTitle

The card title.

const cardTitle: 'text-lg font-semibold leading-none tracking-tight'

cardDescription

The card description.

const cardDescription: 'text-sm text-foreground-secondary'

cardContent

The card content.

const cardContent: 'p-6 pt-0'

cardFooter

The card footer.

const cardFooter: 'flex items-center p-6 pt-0'

badge

Badge component classes with variant support.

const badge: (
  props?:
    | ({
        variant?: 'default' | 'secondary' | 'success' | 'outline' | 'warning' | 'error' | undefined
      } & { class?: string })
    | undefined,
) => string

alert

Alert component classes with variant support (default, info, success, warning, error).

const alert: (
  props?:
    | ({ variant?: 'default' | 'success' | 'warning' | 'error' | 'info' | undefined } & {
        class?: string
      })
    | undefined,
) => string

alertLeftAccent

Left-accent strip for Alert's variant="left-accent" — a 4px colored bar on the leading edge, colored by the alert's STATUS (the base alert cva keys its variants off status too, so the two compose 1:1). Previously the variant was typed in the core contract but silently dropped by every Alert implementation, so left-accent call sites rendered as plain alerts.

const alertLeftAccent: (
  props?:
    | ({ variant?: 'default' | 'success' | 'warning' | 'error' | 'info' | undefined } & {
        class?: string
      })
    | undefined,
) => string

accentCard

A block with the accent bar down its leading edge (see ACCENT_BAR). custom reads --mol-accent, set inline by the caller for data-driven colors.

const accentCard: (
  props?:
    | ({
        tone?:
          'default' | 'success' | 'warning' | 'error' | 'primary' | 'info' | 'custom' | undefined
      } & { class?: string })
    | undefined,
) => string

accentCardLabel

The small uppercase heading of an accent block, in the bar's color.

const accentCardLabel: (
  props?:
    | ({
        tone?:
          'default' | 'success' | 'warning' | 'error' | 'primary' | 'info' | 'custom' | undefined
      } & { class?: string })
    | undefined,
) => string

alertTitle

The alert title.

const alertTitle: 'mb-1 font-medium leading-none tracking-tight'

alertDescription

The alert description.

const alertDescription: 'text-sm [&_p]:leading-relaxed'

avatar

Avatar component classes.

const avatar: (
  props?: ({ size?: 'sm' | 'md' | 'lg' | 'xl' | undefined } & { class?: string }) | undefined,
) => string

avatarImage

The avatar image.

const avatarImage: 'aspect-square h-full w-full object-cover'

avatarFallback

Avatar fallback container classes (centered, rounded, secondary background).

const avatarFallback: 'flex h-full w-full items-center justify-center rounded-full bg-surface-secondary text-foreground-secondary'

dialogOverlay

Modal/Dialog overlay classes — the TINT only. The backdrop blur lives on dialogWrapper (the dialog's PARENT), never here: a backdrop-filter on a same-z sibling relies on DOM paint order, and browser-extension DOM injection (e.g. Bitwarden's autofill overlay) can trigger a Chromium recomposite that momentarily sorts the filter layer above the dialog — blurring the dialog itself. A parent's backdrop-filter can never affect its children, by spec.

const dialogOverlay: 'fixed inset-0 z-[9998] bg-overlay pointer-events-none'

dialogContent

The dialog content.

const dialogContent: (
  props?:
    ({ size?: 'sm' | 'md' | 'lg' | 'xl' | 'full' | undefined } & { class?: string }) | undefined,
) => string

dialogHeader

The dialog header.

const dialogHeader: 'flex-shrink-0 flex items-center justify-between px-6 py-2'

dialogFooter

Dialog footer classes.

const dialogFooter: 'flex-shrink-0 flex flex-col-reverse px-6 py-4 border-t border-border sm:flex-row sm:justify-end sm:space-x-2'

dialogTitle

The dialog title.

const dialogTitle: 'text-xl font-semibold leading-tight tracking-tight'

dialogDescription

The dialog description.

const dialogDescription: 'text-sm text-foreground-secondary'

dialogClose

Dialog close button classes.

const dialogClose: 'flex-shrink-0 ml-4 -mr-4 p-1.5 cursor-pointer text-foreground-secondary hover:text-foreground transition-colors focus:outline-none'

dialogCloseFloating

Floating close for title-less dialogs — overlays the top-right corner of the dialog box (dialogContent is relative) instead of rendering a header row that pushes the body down.

const dialogCloseFloating: 'absolute right-3 top-3 z-10 p-1.5 cursor-pointer text-foreground-secondary hover:text-foreground transition-colors focus:outline-none'

dialogWrapper

The dialog wrapper. Carries the backdrop blur (see dialogOverlay for why it must be on the dialog's parent, not a sibling) and sits one z above the tint overlay so the dialog's stacking never depends on DOM order.

const dialogWrapper: 'fixed inset-0 z-[9999] flex items-center justify-center px-4 py-10 backdrop-blur-[2px]'

dialogBody

The dialog body.

const dialogBody: 'flex-1 min-h-0 overflow-y-auto px-6 py-6'

actionSheet

Action sheet: bottom-anchored panel for pickers and action menus.

const actionSheet: 'fixed bottom-0 left-0 right-0 z-[9998] bg-surface rounded-t-2xl max-h-[50vh] overflow-hidden'

actionSheetHeader

Action sheet header with bottom border.

const actionSheetHeader: 'flex-shrink-0 px-4 py-3 border-b border-border'

Dropdown menu content container classes.

const dropdownContent: 'z-[1000] min-w-[8rem] overflow-hidden rounded-md border bg-surface p-1 text-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95'

Dropdown menu item classes.

const dropdownItem: 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-surface-secondary focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50'

The dropdown separator.

const dropdownSeparator: '-mx-1 my-1 h-px bg-border'

The dropdown label.

const dropdownLabel: 'px-2 py-1.5 text-sm font-semibold'

table

Table root classes.

const table: 'w-full caption-bottom text-sm'

tableHeader

The table header.

const tableHeader: '[&_tr]:border-b'

tableBody

The table body.

const tableBody: '[&_tr:last-child]:border-0'

tableFooter

The table footer.

const tableFooter: 'border-t bg-surface-secondary font-medium [&>tr]:last:border-b-0'

tableRow

Table row classes.

const tableRow: 'border-b transition-colors hover:bg-surface-secondary data-[state=selected]:bg-surface-secondary'

tableHead

Table header cell classes.

const tableHead: 'h-12 px-4 text-left align-middle font-medium text-foreground-secondary [&:has([role=checkbox])]:pr-0'

tableCell

The table cell.

const tableCell: 'p-4 align-middle [&:has([role=checkbox])]:pr-0'

tableCaption

The table caption.

const tableCaption: 'mt-4 text-sm text-foreground-secondary'

tabsList

Tabs list (tablist container) classes.

variant controls shape/background (a full-width underline rail for line; a filled/bordered box for enclosed; a soft or solid pill track for *-rounded); size controls only the container height, so it never conflicts with tabsTrigger's own size-driven padding.

enclosed+md (the defaults) reproduce the single style this used to be hardcoded to, token-for-token.

const tabsList: (
  props?:
    | ({
        variant?: 'line' | 'enclosed' | 'soft-rounded' | 'solid-rounded' | undefined
        size?: 'sm' | 'md' | 'lg' | undefined
      } & { class?: string })
    | undefined,
) => string

tabsTrigger

Tabs trigger (individual tab button) classes.

variant controls the shape/color of the ACTIVE state via data-[state=active]:* attribute selectors — the caller (see @molecule/app-ui-react's Tabs) sets data-state="active"|"inactive" on the element; nothing here reads a JS active flag. size controls padding/font-size only, shared across every variant so it can never conflict with a variant's own classes.

enclosed+md (the defaults) reproduce the single style this used to be hardcoded to, token-for-token.

const tabsTrigger: (
  props?:
    | ({
        variant?: 'line' | 'enclosed' | 'soft-rounded' | 'solid-rounded' | undefined
        size?: 'sm' | 'md' | 'lg' | undefined
      } & { class?: string })
    | undefined,
) => string

tabsContent

Tabs content (panel) classes.

variant only adjusts the top spacing beneath the tablist — enclosed (the default) reproduces the single style this used to be hardcoded to, token-for-token.

const tabsContent: (
  props?:
    | ({ variant?: 'line' | 'enclosed' | 'soft-rounded' | 'solid-rounded' | undefined } & {
        class?: string
      })
    | undefined,
) => string

tooltipContent

Tooltip content container classes.

const tooltipContent: 'z-[1500] max-w-[90vw] overflow-hidden whitespace-nowrap rounded-md border bg-surface px-3 py-1.5 text-sm text-foreground shadow-md animate-in fade-in-0 zoom-in-95'

progress

Progress component classes.

const progress: 'relative h-2 w-full overflow-hidden rounded-full bg-surface-secondary'

progressIndicator

The progress indicator.

const progressIndicator: 'h-full w-full flex-1 bg-primary transition-all'

skeleton

Skeleton component classes.

const skeleton: 'animate-pulse rounded-md bg-surface-secondary'

separator

Separator component classes with horizontal/vertical orientation.

const separator: (
  props?:
    ({ orientation?: 'horizontal' | 'vertical' | undefined } & { class?: string }) | undefined,
) => string

spinner

Spinner component classes with size variants.

const spinner: (
  props?: ({ size?: 'sm' | 'md' | 'lg' | 'xl' | undefined } & { class?: string }) | undefined,
) => string

toastViewport

Toast viewport container classes (positioned fixed, stacked).

const toastViewport: 'fixed top-0 z-[1600] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]'

toast

Tailwind toast notification variant classes (default, success, error, warning).

const toast: (
  props?:
    | ({ variant?: 'default' | 'success' | 'warning' | 'error' | 'info' | undefined } & {
        class?: string
      })
    | undefined,
) => string

toastTitle

The toast title.

const toastTitle: 'text-sm font-semibold'

toastDescription

The toast description.

const toastDescription: 'text-sm opacity-90'

toastClose

Toast close button classes.

const toastClose: 'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100'

toastAction

Toast action button classes.

const toastAction: 'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-surface-secondary focus:outline-none focus:ring-2 disabled:pointer-events-none disabled:opacity-50'

accordion

Accordion component classes.

const accordion: 'w-full'

accordionItem

The accordion item.

const accordionItem: 'border-b'

accordionTrigger

The accordion trigger.

const accordionTrigger: (
  props?: ({ variant?: 'default' | 'bordered' | undefined } & { class?: string }) | undefined,
) => string

accordionContent

Accordion content wrapper classes with collapse animation.

const accordionContent: 'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down'

accordionContentInner

The accordion content inner.

const accordionContentInner: 'pb-4 pt-0'

pagination

Pagination component classes.

const pagination: 'mx-auto flex w-full justify-center'

paginationContent

The pagination content.

const paginationContent: 'flex flex-row items-center gap-1'

paginationItem

The pagination item.

const paginationItem: (
  props?:
    | ({ variant?: 'default' | 'active' | undefined; size?: 'sm' | 'md' | 'lg' | undefined } & {
        class?: string
      })
    | undefined,
) => string

The pagination link.

const paginationLink: 'cursor-pointer'

paginationPrevious

The pagination previous.

const paginationPrevious: 'gap-1 pl-2.5'

paginationNext

The pagination next.

const paginationNext: 'gap-1 pr-2.5'

paginationEllipsis

The pagination ellipsis.

const paginationEllipsis: 'flex h-9 w-9 items-center justify-center'

footerBar

Footer bar classes (full-width). Position is determined by the consuming layout, not forced here.

const footerBar: 'w-full flex flex-wrap items-center justify-around p-[5px] bg-background text-[12px] text-foreground-secondary'

Footer link classes.

const footerLink: 'p-[5px] text-[12px] text-foreground-secondary hover:text-foreground no-underline'

footerButton

Footer button classes.

const footerButton: 'inline-flex items-center gap-1 border-0 m-0 p-[5px] text-[12px] text-foreground-secondary hover:text-foreground bg-transparent cursor-pointer'

languageGrid

Language selector grid container classes.

const languageGrid: 'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2 max-h-96 overflow-y-auto p-2'

languageOption

Language option (inactive) classes. On coarse pointers (touch) the row grows to the 44px touch floor and centers its label with flex (text-center alone cannot center flex items, hence justify-center); fine-pointer rendering is unchanged.

const languageOption: 'px-3 py-2 rounded-md text-sm cursor-pointer text-center transition-colors hover:bg-surface-secondary pointer-coarse:min-h-[44px] pointer-coarse:flex pointer-coarse:items-center pointer-coarse:justify-center'

languageActive

Language option (active/selected) classes. Same coarse-pointer touch floor as languageOption.

const languageActive: 'px-3 py-2 rounded-md text-sm cursor-pointer text-center transition-colors bg-primary/15 text-primary font-medium pointer-coarse:min-h-[44px] pointer-coarse:flex pointer-coarse:items-center pointer-coarse:justify-center'

languagePickerTrigger

Language picker trigger button classes (globe icon + native locale name). Inherits surface/foreground tokens so it sits cleanly inside Settings cards, header chrome, or footers without forcing a specific background.

const languagePickerTrigger: 'inline-flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium bg-transparent text-foreground-secondary hover:text-foreground hover:bg-surface-secondary border border-border-secondary cursor-pointer transition-colors'

themeToggleButton

Theme toggle button classes.

const themeToggleButton: 'h-10 w-10 p-0 border-0 bg-transparent text-foreground-secondary hover:text-foreground cursor-pointer flex items-center justify-center'

oauthDivider

OAuth divider classes (horizontal line with text).

const oauthDivider: 'relative mt-[45px] mb-[15px] text-[15px] text-center uppercase text-foreground-secondary'

oauthDividerLine

OAuth divider horizontal line classes.

const oauthDividerLine: 'absolute top-1/2 left-0 right-0 w-full h-[1px] border-t border-[rgba(170,170,170,0.33)]'

oauthDividerText

The oauth divider text.

const oauthDividerText: 'relative px-[15px] bg-background'

oauthButtonGroup

OAuth provider button group container classes.

const oauthButtonGroup: 'flex flex-wrap gap-[15px] w-full max-w-[330px] mx-auto justify-center items-center'

oauthButton

OAuth provider button classes.

const oauthButton: 'group grow basis-[40px] flex flex-col justify-center items-center min-h-[60px] bg-surface border-0 rounded-[3px] cursor-pointer p-0 transition-all duration-250 hover:bg-surface-hover'

oauthButtonIcon

OAuth provider button icon classes.

const oauthButtonIcon: 'opacity-75 group-hover:opacity-100 text-foreground transition-opacity'

authFormError

Auth form error message classes.

const authFormError: 'text-error mt-[15px]'

Forgot password link classes.

const forgotPasswordLink: 'border-0 p-0 m-0 bg-transparent text-foreground-secondary text-[12px] cursor-pointer hover:text-primary'

iconXs

Icon extra-small size classes (12×12).

const iconXs: 'h-3 w-3'

iconSm

Tailwind classes for small icons (16x16).

const iconSm: 'h-4 w-4'

iconMd

Tailwind classes for medium icons (20x20).

const iconMd: 'h-5 w-5'

buttonIconLeft

Button left icon spacing classes.

const buttonIconLeft: 'shrink-0'

buttonIconRight

The button icon right.

const buttonIconRight: 'shrink-0'

buttonSpinner

The button spinner.

const buttonSpinner: 'shrink-0'

cardInteractive

Interactive card classes (hover shadow effect).

const cardInteractive: 'cursor-pointer hover:shadow-md transition-shadow'

alertIconWrapper

Alert icon wrapper classes.

const alertIconWrapper: 'flex-shrink-0'

alertContent

The alert content.

const alertContent: 'flex-1'

alertDismiss

The alert dismiss.

const alertDismiss: 'flex-shrink-0 ml-2 p-1 rounded hover:bg-black/5 transition-colors'

floatingInputWrapper

Floating input wrapper classes (relative container).

const floatingInputWrapper: 'relative inline-block align-middle w-full'

floatingInput

The floating input.

const floatingInput: 'peer'

floatingLabel

Floating label classes (absolute positioned, animated on focus).

const floatingLabel: 'absolute top-0 left-0 right-0 w-full p-[3px] text-[11px] leading-none whitespace-nowrap text-foreground pointer-events-none transition-all duration-250 peer-hover:bg-input-background peer-focus:text-primary peer-focus:[text-shadow:0_0_var(--color-primary)] peer-focus:bg-input-background'

inputWrapper

Input wrapper classes.

const inputWrapper: 'w-full'

inputInner

The input inner.

const inputInner: 'relative'

inputLeftElement

Input left addon/icon container classes.

const inputLeftElement: 'absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-foreground-secondary'

inputRightElement

The input right element.

const inputRightElement: 'absolute inset-y-0 right-0 flex items-center pr-3'

inputClearButton

The input clear button.

const inputClearButton: 'text-foreground-secondary hover:text-foreground'

inputPadLeft

The input pad left.

const inputPadLeft: 'pl-10'

inputPadRight

The input pad right.

const inputPadRight: 'pr-10'

selectNative

Native select appearance reset classes.

const selectNative: 'appearance-none'

labelBlock

Block-level label classes.

const labelBlock: 'mb-1.5 block'

formFieldset

The form fieldset.

const formFieldset: 'space-y-4'

formFieldsetContents

The form fieldset contents.

const formFieldsetContents: 'contents'

formField

The form field.

const formField: 'space-y-1.5'

formFieldWrapper

The form field wrapper.

const formFieldWrapper: 'flex flex-col'

controlLabel

Control (checkbox/switch/radio) label wrapper classes.

const controlLabel: 'flex items-center gap-2 cursor-pointer'

controlContainer

The control container.

const controlContainer: 'flex items-center'

controlText

The control text.

const controlText: 'text-sm'

controlDisabled

The control disabled.

const controlDisabled: 'opacity-50 cursor-not-allowed'

radioGroupLabel

Radio group label classes.

const radioGroupLabel: 'text-sm font-medium mb-2 block'

accordionChevron

Accordion chevron icon classes (rotates on open).

const accordionChevron: 'h-4 w-4 shrink-0 transition-transform duration-200'

accordionTriggerBase

The accordion trigger base.

const accordionTriggerBase: 'w-full text-left'

tabsFitted

Fitted (full-width) tabs container classes.

const tabsFitted: 'w-full'

tabTriggerFitted

The tab trigger fitted.

const tabTriggerFitted: 'flex-1'

tabTriggerIcon

The tab trigger icon.

const tabTriggerIcon: 'mr-2'

paginationInteractive

Pagination interactive item (cursor pointer).

const paginationInteractive: 'cursor-pointer'

progressWrapper

Progress bar wrapper classes.

const progressWrapper: 'w-full'

progressLabelContainer

The progress label container.

const progressLabelContainer: 'flex justify-between mb-1 text-sm'

progressLabelText

The progress label text.

const progressLabelText: 'text-foreground-secondary'

progressIndeterminate

The progress indeterminate.

const progressIndeterminate: 'animate-indeterminate-progress'

tableWrapper

Table wrapper classes (scrollable container).

const tableWrapper: 'relative w-full overflow-auto'

tableLoadingOverlay

Table loading overlay classes (semi-transparent centered spinner container).

const tableLoadingOverlay: 'absolute inset-0 bg-background/50 flex items-center justify-center z-10'

tableEmptyCell

The table empty cell.

const tableEmptyCell: 'py-10 text-center text-foreground-secondary'

tableSortWrapper

The table sort wrapper.

const tableSortWrapper: 'flex items-center gap-1'

tableSortIcon

The table sort icon.

const tableSortIcon: 'ml-1'

tableRowStriped

The table row striped.

const tableRowStriped: 'bg-surface-secondary/50'

tableRowHoverable

The table row hoverable.

const tableRowHoverable: 'hover:bg-surface-secondary'

tableRowClickable

The table row clickable.

const tableRowClickable: 'cursor-pointer'

tableHeadSortable

The table head sortable.

const tableHeadSortable: 'cursor-pointer select-none hover:bg-surface-secondary'

tableBordered

The table bordered.

const tableBordered: 'border'

toastIconWrapper

Toast icon wrapper classes.

const toastIconWrapper: 'flex-shrink-0'

toastContentWrapper

The toast content wrapper.

const toastContentWrapper: 'flex-1 gap-1'

Dropdown trigger wrapper classes.

const dropdownTrigger: 'inline-block'

The dropdown item icon.

const dropdownItemIcon: 'mr-2 flex-shrink-0'

The dropdown item label.

const dropdownItemLabel: 'flex-1'

The dropdown item shortcut.

const dropdownItemShortcut: 'ml-auto text-xs text-foreground-tertiary'

The dropdown item disabled.

const dropdownItemDisabled: 'opacity-50 cursor-not-allowed'

tooltipTrigger

Tooltip trigger wrapper classes.

const tooltipTrigger: 'inline-block'

avatarInitials

Avatar initials text classes.

const avatarInitials: 'text-sm font-medium'

avatarFallbackIcon

The avatar fallback icon.

const avatarFallbackIcon: 'h-1/2 w-1/2'

avatarSquare

The avatar square.

const avatarSquare: 'rounded-md'

badgeSquare

Badge with square (rounded-md) corners.

const badgeSquare: 'rounded-md'

skeletonTextContainer

Skeleton text placeholder container (vertical spacing).

const skeletonTextContainer: 'space-y-2'

skeletonCircle

The skeleton circle.

const skeletonCircle: 'rounded-full'

skeletonWave

Skeleton wave animation classes (shimmer gradient effect).

const skeletonWave: 'animate-shimmer bg-gradient-to-r from-surface-secondary via-surface to-surface-secondary bg-[length:200%_100%]'

skeletonNone

The skeleton none.

const skeletonNone: 'animate-none'

displayBlock

Display block utility class.

const displayBlock: 'block'

displayInlineBlock

The display inline block.

const displayInlineBlock: 'inline-block'

displayContents

The display contents.

const displayContents: 'contents'

mxAuto

Horizontal auto-margin centering utility class.

const mxAuto: 'mx-auto'

cursorPointer

The cursor pointer.

const cursorPointer: 'cursor-pointer'

roundedFull

The rounded full.

const roundedFull: 'rounded-full'

textRight

Text right-alignment utility class.

const textRight: 'text-right'

logoText

Logo text classes (tracked, 26px).

const logoText: 'text-[26px] leading-[26px] tracking-[-1px]'

logoIcon

Tailwind class for the logo icon color.

const logoIcon: 'text-primary'

headerFixed

Header container classes (full-width). Position is determined by the consuming layout, not forced here.

const headerFixed: 'top-0 left-0 right-0 w-full'

headerInner

The header inner.

const headerInner: 'w-full max-w-[1280px] mx-auto h-10 p-[5px]'

authHeaderInner

Auth page header inner container classes.

const authHeaderInner: 'w-full h-10 p-[5px]'

authPageBody

The auth page body.

const authPageBody: 'flex flex-col flex-1 items-center justify-center px-4 pb-[30px]'

authFormWrapper

The auth form wrapper.

const authFormWrapper: 'w-max min-w-[330px] max-w-[90vw] py-[30px] mx-auto text-center'

authField

The auth field.

const authField: 'mb-[15px]'

authAfterSubmit

The auth after submit.

const authAfterSubmit: 'mt-[15px]'

authButtonRow

Auth button row (flex container).

const authButtonRow: 'flex'

authBackButton

The auth back button.

const authBackButton: 'w-[45px] mr-[10px]'

authSubmitButton

The auth submit button.

const authSubmitButton: 'grow whitespace-nowrap'

authLoginSignup

The auth login signup.

const authLoginSignup: 'grow whitespace-nowrap mr-[10px]'

authLoginSubmit

The auth login submit.

const authLoginSubmit: 'grow whitespace-nowrap'

authLoginSubmitFull

The auth login submit full.

const authLoginSubmitFull: 'w-full whitespace-nowrap'

authHidden

The auth hidden.

const authHidden: 'hidden'

authArrowIcon

The auth arrow icon.

const authArrowIcon: 'ml-[2.5px]'

appLayout

Main app layout classes (min-height, horizontal/bottom padding). No top padding: the shell renders the header IN FLOW (AppHeader's fixed default contributes no position utility — headerFixed is offsets only), so a top pad cleared a header that never overlapped the content and every page opened with a phantom 55px gap. Apps with a genuinely fixed/sticky header add their own top padding.

const appLayout: 'min-h-screen px-[15px] pb-[55px]'

flex1

Flex grow utility class (flex-1).

const flex1: 'flex-1'

oauthProviderLabel

OAuth provider label (fallback when no icon).

const oauthProviderLabel: 'text-sm font-medium'

surfaceSecondary

Secondary surface background utility class.

const surfaceSecondary: 'bg-surface-secondary'

textPrimary

Primary text color utility class.

const textPrimary: 'text-primary'

bgPrimaryContainer

Primary-tinted container surface; pair with textOnPrimaryContainer.

const bgPrimaryContainer: 'bg-primary-container'

textOnPrimaryContainer

Readable text on bgPrimaryContainer.

const textOnPrimaryContainer: 'text-on-primary-container'

textSuccess

The text success.

const textSuccess: 'text-success'

textWarning

The text warning.

const textWarning: 'text-warning'

borderB

Bottom border with theme border color.

const borderB: 'border-b border-border'

borderT

Tailwind classes for a top border with the theme border color.

const borderT: 'border-t border-border'

borderR

Tailwind classes for a right border with the theme border color.

const borderR: 'border-r border-border'

borderL

Tailwind classes for a left border with the theme border color.

const borderL: 'border-l border-border'

borderAll

The border all.

const borderAll: 'border border-border'

borderBPrimary

The border b primary.

const borderBPrimary: 'border-b-2 border-b-primary'

bgErrorSubtle

Subtle error background utility class.

const bgErrorSubtle: 'bg-error-light'

bgBorder

Tailwind class for a background matching the theme border color.

const bgBorder: 'bg-border'

pageShell

PageShell root — flex row filling the screen.

Use this pattern (or an equivalent flex row on the page root) whenever a persistent sidebar and <main> are siblings. A full-height sticky sidebar in block flow (no flex row on the parent) still occupies vertical space, so <main> with only ml-[…] starts below the viewport — content appears under fixed headers / near the bottom. Prefer pageShell + pageShellSidebar, or a fixed inset-y-0 sidebar plus horizontal offset on main.

const pageShell: 'flex h-screen overflow-hidden'

pageShellSidebar

PageShell sidebar — fixed-width left panel.

const pageShellSidebar: 'w-60 flex-shrink-0 border-r border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 overflow-y-auto transition-all duration-300'

pageShellSidebarCollapsed

PageShell sidebar collapsed modifier — narrow width.

const pageShellSidebarCollapsed: 'w-16'

pageShellTopbar

PageShell top bar — horizontal strip above content.

const pageShellTopbar: 'h-14 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 flex items-center px-4 gap-3'

pageShellContent

PageShell main content area.

const pageShellContent: 'flex-1 overflow-y-auto'

pageShellSidebarToggle

PageShell sidebar collapse/expand toggle button.

const pageShellSidebarToggle: 'p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors'

emptyState

EmptyState root — centered column layout.

const emptyState: 'flex flex-col items-center justify-center py-16 px-4 text-center'

emptyStateIcon

EmptyState icon wrapper.

const emptyStateIcon: 'mb-4 text-gray-400 dark:text-gray-500'

emptyStateTitle

EmptyState heading.

const emptyStateTitle: 'text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2'

emptyStateDescription

EmptyState description.

const emptyStateDescription: 'text-sm text-gray-500 dark:text-gray-400 mb-6 max-w-md'

emptyStateAction

EmptyState action slot.

const emptyStateAction: ''

PageHeader root container.

const pageHeader: 'mb-6'

pageHeaderTitle

PageHeader title.

const pageHeaderTitle: 'text-2xl font-bold text-gray-900 dark:text-gray-100'

pageHeaderDescription

PageHeader description.

const pageHeaderDescription: 'mt-1 text-sm text-gray-500 dark:text-gray-400'

pageHeaderActions

PageHeader action buttons container.

const pageHeaderActions: 'flex gap-2 mt-4 sm:mt-0'

pageHeaderBreadcrumbs

PageHeader breadcrumb trail.

const pageHeaderBreadcrumbs: 'flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400 mb-2'

pageHeaderBreadcrumbItem

PageHeader breadcrumb item.

const pageHeaderBreadcrumbItem: 'hover:text-gray-700 dark:hover:text-gray-200'

pageHeaderBreadcrumbSeparator

PageHeader breadcrumb separator.

const pageHeaderBreadcrumbSeparator: 'mx-1'

searchField

SearchBox field: a bordered bar; the ring appears while anything inside has focus.

const searchField: 'relative flex h-11 w-full items-center gap-2 rounded-xl border border-border bg-input-background px-3 text-foreground transition-colors focus-within:border-primary focus-within:shadow-[0_0_0_3px_rgba(64,112,224,0.18)]'

searchFieldIcon

SearchBox leading icon slot.

const searchFieldIcon: 'flex flex-none items-center text-foreground-secondary'

searchFieldInput

SearchBox input: borderless, fills the bar, left-aligned, visible placeholder.

const searchFieldInput: 'h-full min-w-0 flex-1 border-0 bg-transparent p-0 text-left text-[15px] leading-none text-foreground placeholder:text-foreground-secondary placeholder:opacity-100 focus:outline-none focus-visible:outline-none [&::-webkit-search-cancel-button]:hidden'

searchFieldActions

SearchBox trailing slot.

const searchFieldActions: 'flex flex-none items-center gap-1.5'

searchFieldClear

SearchBox icon-only clear button.

const searchFieldClear: 'flex h-7 w-7 items-center justify-center rounded-md text-foreground-secondary hover:bg-surface-secondary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary'

searchStatusRow

SearchBox status row: chips left, count and tips right.

const searchStatusRow: 'mt-2 flex min-h-6 flex-wrap items-center gap-1.5 text-[13px] text-foreground-secondary'

searchTips

SearchBox syntax tips line.

const searchTips: 'mt-1 text-[13px] leading-relaxed text-foreground-secondary'

kbd

A key cap.

const kbd: 'inline-flex h-5 min-w-5 items-center justify-center rounded-md border border-border px-1.5 font-mono text-[11px] leading-none text-foreground-secondary'

filterChip

A removable filter chip.

const filterChip: 'inline-flex cursor-pointer items-center gap-1 rounded-full border border-primary/40 bg-primary/10 px-2.5 py-0.5 text-xs font-medium text-foreground transition-colors hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary'

filterChipNegated

Added to a filter chip that excludes its value.

const filterChipNegated: 'border-warning/50 bg-warning/10'

filterChipRemove

The × inside a filter chip.

const filterChipRemove: 'text-foreground-secondary'

resultList

SearchResults list container.

const resultList: 'm-0 mt-4 list-none divide-y divide-border overflow-hidden rounded-xl border border-border bg-surface p-0'

resultItem

One result row.

const resultItem: 'relative flex cursor-pointer items-start gap-3 px-4 py-3 transition-colors hover:bg-surface-secondary'

resultItemActive

The active result row.

const resultItemActive: 'bg-surface-secondary before:absolute before:bottom-2 before:left-0 before:top-2 before:w-0.5 before:rounded-full before:bg-primary'

resultBody

A result row's body: the link that fills the row.

const resultBody: 'block min-w-0 flex-1 no-underline'

resultTitle

A result row's title.

const resultTitle: 'font-semibold text-foreground'

resultTitleMono

Added to a result title that is code.

const resultTitleMono: 'font-mono text-[0.95em]'

resultMeta

A result row's meta line.

const resultMeta: 'text-xs text-foreground-secondary'

resultDescription

A result row's description.

const resultDescription: 'mt-0.5 line-clamp-2 text-[13.5px] leading-snug text-foreground-secondary'

resultMark

A matched term inside a result.

const resultMark: 'rounded-[3px] bg-primary/20 px-0.5 text-inherit'

resultAction

A result row's trailing action slot.

const resultAction: 'flex-none self-center'

resultEmpty

A results empty state.

const resultEmpty: 'py-10 text-center text-[13.5px] text-foreground-secondary'

container

Container component classes.

const container: (
  props?:
    | ({ size?: 'sm' | 'md' | 'lg' | 'xl' | 'full' | '2xl' | undefined } & { class?: string })
    | undefined,
) => string

flex

Flex layout classes.

const flex: (
  props?:
    | ({
        direction?: 'row' | 'row-reverse' | 'col' | 'col-reverse' | undefined
        align?: 'center' | 'start' | 'end' | 'stretch' | 'baseline' | undefined
        justify?: 'center' | 'start' | 'end' | 'between' | 'around' | 'evenly' | undefined
        wrap?: 'wrap' | 'nowrap' | 'wrap-reverse' | undefined
        gap?: 'sm' | 'md' | 'lg' | 'xl' | 'none' | 'xs' | undefined
      } & { class?: string })
    | undefined,
) => string

grid

Grid layout classes.

These cols values are the FIXED (non-responsive) column classes — a grid that is cols-wide at every viewport width. The ClassMap grid resolver (see classMap.ts) uses these only for the opt-out (responsive: false) and single-column paths; by DEFAULT it emits the mobile-first ramp in gridResponsiveCols instead.

const grid: (
  props?:
    | ({
        cols?: 1 | 2 | 3 | 4 | 5 | 6 | 12 | undefined
        gap?: 'sm' | 'md' | 'lg' | 'xl' | 'none' | 'xs' | undefined
      } & { class?: string })
    | undefined,
) => string

gridResponsiveCols

Mobile-first responsive column ramps for cm.grid({ cols }).

Each value is a COMPLETE, literal Tailwind class string (never built by template interpolation) so Tailwind's scanner picks it up from this package's compiled dist (see the @source note in index.ts), and base.css safelists the same set. A multi-column grid starts at one column on phones and steps up to the requested cols at sm/lg/xl, so KPI / listing / card grids COLLAPSE on mobile instead of overflowing. cols: 1 stays single-column. The ClassMap grid resolver uses this as the DEFAULT for cols >= 2; pass responsive: false there for the fixed grid-cols-N from the grid CVA above. Every emitted token is a real Tailwind utility (grid-cols-1..6, grid-cols-12, and the sm:/lg:/xl: variants of each).

const gridResponsiveCols: Record<number, string>

stack

Stack layout classes (vertical flexbox).

const stack: (
  props?:
    | ({
        align?: 'center' | 'start' | 'end' | 'stretch' | undefined
        gap?: 'sm' | 'md' | 'lg' | 'xl' | 'none' | 'xs' | undefined
      } & { class?: string })
    | undefined,
) => string

hstack

HStack layout classes (horizontal flexbox).

const hstack: (
  props?:
    | ({
        justify?: 'center' | 'start' | 'end' | 'between' | undefined
        gap?: 'sm' | 'md' | 'lg' | 'xl' | 'none' | 'xs' | undefined
      } & { class?: string })
    | undefined,
) => string

center

Center layout classes.

const center: 'flex items-center justify-center'

srOnly

Screen reader only classes.

const srOnly: 'sr-only'

notSrOnly

The not sr only.

const notSrOnly: 'not-sr-only'

cn

Merges class names, filtering out falsy values. Supports strings, numbers, conditional objects, and nested arrays.

When a class merger is registered via setClassMerger (e.g. the Tailwind bond registers tailwind-merge), conflicting utilities such as two gap-* classes are resolved by it; otherwise the joined string is returned as-is.

const cn: (...classes: ClassValue[]) => string
  • classes — Class values to merge (strings, booleans, objects, arrays).

Returns: A single space-separated class string.

cva

Creates a class variance authority (CVA) function for component variants. Given a base class and variant configuration, returns a function that resolves the final class string based on selected variants.

const cva: <T extends Record<string, Record<string, string>>>(
  base: string,
  config?: CVAConfig<T>,
) => (props?: { [K in keyof T]?: keyof T[K] } & { class?: string }) => string
  • base — The base class that is always included.
  • config — Variant definitions, defaults, and compound variants.

Returns: A function that accepts variant props and returns the resolved class string.

Core Interface

Implements @molecule/app-ui interface.

Injection Notes

Requirements

Peer dependencies:

  • @molecule/app-styling ^1.0.1
  • @molecule/app-ui ^1.2.0

Runtime Dependencies

  • @molecule/app-styling
  • @molecule/app-ui

Theming / where colors come from: the class strings here use semantic tokens (bg-primary, text-primary, bg-surface, …), NOT literal palette classes (bg-blue-600). Those tokens are defined in this package's base.css @theme block, where the ~15 CORE colors read --color-primary: var(--mol-color-primary, <default>) (from the @molecule/app-theme bond) and every other token (hover, container, Material on-*, surface tiers) is AUTO-DERIVED from the cores via color-mix() — so changing one core recolors all its shades.

To recolor a scaffolded app: edit app/src/theme.css (the --color-* variables, :root = light, [data-mol-mode='dark'] = dark) when it exists — it overrides everything here. Only when there is no theme.css do the --mol-color-* from the theme bond drive the palette. NEVER hardcode a literal Tailwind color class in a component — it breaks theming and the safelist.