Best Practices

Guidelines for consuming Gloss components in your application. Follow these conventions to maintain clean architecture, type safety, and predictable behavior.

DO / DON'T Reference

These rules define the contract between Gloss library components and the consuming application. Library components follow the left column; consuming apps should respect the same boundaries.

DODON'T
Accept all data via typed propsRead from React context inside library components
Fire callbacks on user interaction (onChange, onSelect)Write to localStorage or sessionStorage
Use CSS var() with fallbacks for design tokensHardcode hex values or import token CSS directly
Import siblings via relative paths inside library codeImport from the top-level barrel internally (circular dependency)
Export types alongside components from family barrelUse any or @ts-ignore in type contracts
Provide fixture data for component explorer testingDepend on API calls or external data fetching

Consumption Patterns

How to install, import, and configure Gloss packages in your application.

Installation

Install all required packages:

npm install @so.boss/gloss-ui @so.boss/gloss-styles @so.boss/gloss-tokens @so.boss/gloss-utils

Importing Components

Import components and types by name from the package barrel. Use type-only imports for TypeScript interfaces to ensure they are stripped from the production bundle.

// Components
import { MetricCard, Button, TabbedDataView } from '@so.boss/gloss-ui';

// Types (type-only import -- stripped at build time)
import type { MetricCardProps, TabConfig } from '@so.boss/gloss-ui';

// Icons
import { Icon } from '@so.boss/gloss-icons';

Importing Styles

Import the component CSS in your application entry point so styles are available globally:

// In your app's entry file (e.g., main.tsx or layout.tsx)
import '@so.boss/gloss-ui/styles.css';

SCSS Token Access (Vite)

If your SCSS files need access to Gloss design tokens, configure the Vite preprocessor to resolve the token path:

// vite.config.ts
export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@so.boss/gloss-styles/tokens" as *;`,
        loadPaths: ['node_modules'],
      },
    },
  },
});

TypeScript

All components ship with TypeScript declarations. Props are fully typed — use your editor's autocomplete to discover available props and their types. No additional @types/ packages are needed.

State Management Rules

Gloss components never manage their own state. The consuming application owns all state: values, selections, visibility, loading flags, and sort order. Components communicate changes exclusively via callback props.

RuleRationale
Container owns ALL stateLibrary components are stateless renderers
Pass state as controlled propsvalue, activeTabId, sortModel
Pass callbacks for mutationsonSort, onRemove, onTabClose
Memoize container valuesPrevent unnecessary re-renders of library components
Never lift library internal stateAnimation, hover, layout stay inside the library

Example: Managing TabbedDataView Tabs

import { TabbedDataView } from '@so.boss/gloss-ui';
import type { TabConfig } from '@so.boss/gloss-ui';

function DashboardTabs() {
  // App owns tab state
  const [tabs, setTabs] = useState<TabConfig[]>([
    { id: 'orders', type: 'orders', name: 'Orders' },
    { id: 'items', type: 'items', name: 'Items' },
  ]);
  const [activeTabId, setActiveTabId] = useState('orders');

  const handleClose = (id: string) => {
    setTabs(prev => prev.filter(t => t.id !== id));
    if (activeTabId === id) {
      setActiveTabId(tabs[0]?.id ?? '');
    }
  };

  return (
    <TabbedDataView
      tabs={tabs}
      activeTabId={activeTabId}
      onTabChange={setActiveTabId}
      onTabClose={handleClose}
      renderTabContent={(tab) => <DataView type={tab.type} />}
    />
  );
}

Example: Managing MetricCardGrid Card Order

import { MetricCardGrid, MetricCard } from '@so.boss/gloss-ui';

function MetricsDashboard() {
  // App owns card order (drag-and-drop reordering)
  const [cardOrder, setCardOrder] = useState(['revenue', 'orders', 'aov']);
  const [page, setPage] = useState(0);

  return (
    <MetricCardGrid
      cards={cardData}
      cardOrder={cardOrder}
      layout={gridLayout}
      onReorder={setCardOrder}      // Library tells you the new order
      onPageChange={setPage}         // Library tells you the new page
      renderCard={(card) => <MetricCard {...card.props} />}
    />
  );
}

Directory Structure Convention

Every component family in the library follows this canonical layout. When extending the library or understanding its internals, expect this structure:

ComponentName/
  types.ts              -- Type contracts (interfaces, discriminated unions)
  ComponentName.tsx     -- Main component
  SubComponent.tsx      -- Optional sub-components
  utils/                -- Optional pure utility functions
  fixtures/             -- Test/demo fixture data
    fixtureFile.ts      -- Named exports of typed fixture objects
  index.ts              -- Family barrel (re-exports components + types)

Barrel Export Chain

Each family has its own barrel (index.ts) that re-exports components and types. The top-level barrel re-exports all families. External consumers always import from the top-level package:

ComponentName/index.ts    -- exports components + types for one family
       |
       v
@so.boss/gloss-ui (barrel)    -- re-exports all families
       |
       v
Your app                 -- import { MetricCard } from '@so.boss/gloss-ui'

Internal Import Rules

Inside the library

  • Between families: use relative paths (../FilterChip/FilterChip)
  • Within a family: use local relative paths (./types, ./utils/formatValue)

Never do this

  • Import from the top-level barrel inside library code (creates circular dependencies)

Working with Fixtures

Fixtures are typed sample data objects located in each component family's fixtures/ directory. They provide realistic data for prototyping, testing, and component explorer demos.

What Fixtures Provide

  • Typed sample props for every component variant
  • State fixtures: loading, error, empty, stale
  • Realistic data shapes matching production patterns
  • Zero external dependencies — pure data objects

Using Fixtures in Development

// Import fixture data directly during development
import { allVariantFixtures } from '@so.boss/gloss-ui/MetricCard/fixtures/allVariants';
import { orderRows } from '@so.boss/gloss-ui/Table/fixtures/orderRows';

// Use in tests
describe('MetricCard', () => {
  it('renders all variants', () => {
    allVariantFixtures.forEach(fixture => {
      render(<MetricCard {...fixture} />);
    });
  });
});

// Use in prototyping
function Prototype() {
  return (
    <div>
      {allVariantFixtures.map(card => (
        <MetricCard key={card.label} {...card} />
      ))}
    </div>
  );
}

Key benefit

Fixtures are not exported from the public barrel — they are development-only imports. This keeps them out of production bundles while making them available for Storybook stories, unit tests, and rapid prototyping.