Skip to content

Repository files navigation

Properties

Domain-neutral property controls for React applications built on COSS, Base UI, and Tailwind CSS.

The package focuses on compact, composable primitives for common property patterns such as select, person, multi-person, and date. It gives you the interaction model, accessible trigger composition, and formatting hooks. Your application keeps ownership of business meaning, permissions, validation, persistence, and remote state.

What is included

  • SelectProperty: single-choice property control with typed options
  • PersonProperty: person reference control with avatar, name, and optional description
  • MultiPersonProperty: multi-select person control with compact selected-value summary
  • DateProperty: formatted date control with calendar popover and clear action
  • core: lightweight domain types without React or visual dependencies

The public surface is intentionally neutral. The package does not define product-specific controls such as StatusProperty, PriorityProperty, OwnerProperty, or AssigneeProperty.

Design principles

  • Neutral by default: consumer code decides the business label, iconography, and copy
  • Button-based trigger surface: the interactive trigger is a Button, not a Badge
  • Controlled-first API: value, callbacks, and selection state stay explicit
  • COSS-first distribution: registry dependencies are declared so the package can be copied into a consuming app
  • No universal renderer: each primitive is intentionally specific to its interaction model

Requirements

  • React 19
  • Base UI 1.x
  • Tailwind CSS 4
  • COSS registry primitives available in the consuming project

Installation

Install the published package:

pnpm add @tc96/properties
# or
bun add @tc96/properties
# or
npm install @tc96/properties

Install the peer dependencies in the consumer:

pnpm add react react-dom @base-ui/react tailwindcss

COSS registry setup

This package is distributed both as npm code and as a registry item. The registry route copies the source into the consuming app and points to @components/patterns/properties.

Add the COSS aliases in components.json:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "tsx": true,
  "aliases": {
    "components": "@/components",
    "ui": "@/components/ui",
    "patterns": "@/components/patterns"
  }
}

Then install the registry item:

bun add @tc96/properties
bunx shadcn@latest add ./node_modules/@tc96/properties/registry/properties.json

The registry item declares these primitives as dependencies:

  • @coss/avatar
  • @coss/button
  • @coss/calendar
  • @coss/popover
  • @coss/select

Tailwind should scan the installed package:

@import "tailwindcss";
@source "../node_modules/@tc96/properties/dist";

Quick start

SelectProperty

import { SelectProperty, type SelectPropertyOption } from "@tc96/properties";

type Category = "alpha" | "beta";

const categories: SelectPropertyOption<Category>[] = [
  { id: "alpha", label: "Alpha" },
  { id: "beta", label: "Beta" },
];

export function CategoryProperty({
  value,
  onChange,
}: {
  value: Category;
  onChange: (value: Category) => void;
}) {
  return (
    <SelectProperty
      ariaLabel="Category"
      options={categories}
      value={value}
      onValueChange={onChange}
    />
  );
}

PersonProperty

import { PersonProperty } from "@tc96/properties";

const people = [
  {
    value: "alex",
    name: "Alex Rivera",
    description: "alex@example.com",
    avatar: { src: alexAvatarUrl, fallback: "AR" },
  },
];

<PersonProperty
  ariaLabel="Reviewer"
  options={people}
  placeholder="Select a person"
  value={reviewerId}
  onValueChange={setReviewerId}
/>

name is the primary label, description is optional secondary text, and avatar may provide an image source plus fallback initials.

MultiPersonProperty

<MultiPersonProperty
  ariaLabel="Assignees"
  options={people}
  placeholder="Add assignees"
  value={assigneeIds}
  onValueChange={setAssigneeIds}
/>

DateProperty

<DateProperty
  ariaLabel="Target date"
  fallback="No date"
  locale="en-US"
  timeZone="UTC"
  value={targetDate}
  onValueChange={setTargetDate}
/>

Date formatting, serialization, calendar placement, and clear behavior are configurable.

Ownership boundary

Package owns Consumer owns
accessible trigger composition business meaning of each property
generic option, person, and date presentation authorization and available options
controlled values and callbacks remote state, validation, and mutations
date parsing, formatting, and serialization hooks storage format and time policies
read-only presentation workflows, navigation, and side effects

Core contract

@tc96/properties/core exports lightweight definitions without React or visual dependencies:

import type { PropertyDefinition } from "@tc96/properties/core";

const category: PropertyDefinition<"select"> = {
  id: "category",
  label: "Category",
  type: "select",
};

Use the core contract when you need domain types without rendering primitives.

Development

Use Bun 1.3.14 and Node 24.18.0.

bun install
bun run lint:ci
bun run typecheck
bun test
bun run build

License

MIT © Gabriel Melo.

About

Domain-neutral property views for React SaaS applications using COSS and Base UI.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages