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.
| DO | DON'T |
|---|---|
| Accept all data via typed props | Read 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 tokens | Hardcode hex values or import token CSS directly |
| Import siblings via relative paths inside library code | Import from the top-level barrel internally (circular dependency) |
| Export types alongside components from family barrel | Use any or @ts-ignore in type contracts |
| Provide fixture data for component explorer testing | Depend 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-utilsImporting 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.
| Rule | Rationale |
|---|---|
| Container owns ALL state | Library components are stateless renderers |
| Pass state as controlled props | value, activeTabId, sortModel |
| Pass callbacks for mutations | onSort, onRemove, onTabClose |
| Memoize container values | Prevent unnecessary re-renders of library components |
| Never lift library internal state | Animation, 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.