Silicon
A customized, accessible, and fully typed React component library. It pairs core UI primitives with higher-level building blocks, all themeable through CSS custom properties.
Install
npm install @fairshare/silicon
React and React DOM are peer dependencies. React 18 and 19 are supported.
Icons come from Font Awesome Pro (Regular for the kit, Solid for IconByName and IconPicker), so installing needs a Pro
npm token. Point the @fortawesome scope at Font Awesome's registry (this repo's .npmrc does) and set the token once:
npm config set "//npm.fontawesome.com/:_authToken" YOUR_TOKEN
Use
Import the stylesheet once near the root of your application, then import components from the package:
import '@fairshare/silicon/styles.css'
import { Badge, Button, Card } from '@fairshare/silicon'
export function Project() {
return (
<Card title="Project" extra={<Badge variant="success">Active</Badge>}>
<Button>Open</Button>
</Card>
)
}
The package ships as ESM and includes TypeScript declarations.
Components
- Primitives: Accordion, Avatar, Badge, Banner, Board, Breadcrumbs, Button, Card, Checkbox, Combobox, Dialog, Drawer, EditableText, Field, Filters, IconPicker, Input, Label, Menu, Meter, NumberField, Pagination, Popover, Progress, RadioGroup, RowMenu, Select, Settings, Skeleton, Slider, Spinner, Switch, Table, Tabs, TagInput, Toast, Tooltip
- Building blocks: AgentStatus, ApprovalGate, Attachments, Branch, ChainOfThought, Checkpoint, CodeBlock, Commit, ConfidenceBadge, Connection, Conversation, DiffView, FeedbackButtons, InlineCitation, Message, MessageActions, OrgSwitcher, Plan, PromptInput, Queue, Shimmer, Sidebar, Sources, StackTrace, StatCard, StreamingText, SuggestionChip, Terminal, TestResults, ThinkingBlock, TokenMeter, ToolCallTimeline, Tree, UserProfile, WebPreview
Component props and supporting data types are exported from the same entry point.
Desktop windows
WindowManager supplies a bounded workspace with draggable, resizable AppWindow
surfaces. Launch the development server and visit /#windows for the light-themed
demo (the demo also has a dark-mode toggle).
import { Button, WindowManager } from '@fairshare/silicon'
<WindowManager
windows={[
{
id: 'notes',
title: 'Notes',
defaultBounds: { x: 24, y: 24, width: 440, height: 320 },
content: <textarea aria-label="Notes" />,
},
]}
style={{ height: 600 }}
>
{manager => <Button onClick={() => manager.open('notes')}>Open notes</Button>}
</WindowManager>
- Drag the title bar freely past the workspace edges; windows can remain partly offscreen, with a small title-bar grip kept visible on release. The desktop clips offscreen content. Resize any edge or corner. Pointer cancellation rolls back the gesture. Double-click the title bar to maximize or restore.
- Drag the pointer within 24px of the workspace's left or right edge to preview a half-width layout; the preview stays active beyond either side edge. Release to snap. Escape or pointer cancellation dismisses the preview. Dragging a snapped title bar away restores its floating size.
- Each title bar has an Arrange menu for Left half, Right half, and Restore floating window. With the title bar focused, Alt + Left/Right snaps and Alt + Down restores. The demo's Side by side button arranges both apps.
- Snap Assist offers other open or minimized windows in the empty half. Select one to form a pair. Escape, the dismiss button, or clicking outside the picker leaves the first window snapped. Closed windows are excluded; no picker appears when there are no eligible windows or insufficient horizontal space.
- Paired windows share a divider. Drag it to resize both together, respecting both minimum widths. Focus it and use Left/Right (10px), Home/End (limits), or double-click to reset to equal halves. Escape/pointer cancellation rolls back a divider drag. Ratios are saved only when a drag finishes.
snapPairinWindowManagerStatepreserves the pair and its ratio. The divider hides when either window is minimized/maximized or the workspace is too narrow, then returns when both are visible. Floating, closing, or removing a paired window dissolves the pair. App content stays mounted through pairing.- Custom shells can use
snapAssistId,completeSnapAssist(id),dismissSnapAssist(), andsetSplitRatio(ratio).SnapAssistis exported as a presentational picker. Picker state is transient and isn't persisted. - Use
manager.snap(id, 'left' | 'right')andmanager.restore(id)in custom taskbars. Snapping keeps normal bounds and is included in saved layouts through the optionalsnapproperty. Maximizing a snapped window and restoring it returns to that half; restoring through Arrange returns to floating bounds. - Snapped halves track workspace size. If the workspace is narrower than twice
a window's
minWidth, an existing snapped window uses the full width and returns to its half when space is available. Edge snapping and menu actions are disabled at that size; the programmatic API can still select a snap side. - Tab to a title bar and use arrow keys to move by 10px, or Shift + arrows to resize. Window controls have accessible labels. Focusing content raises its window.
- Minimize keeps application content mounted. Close unmounts it; reopening uses
its retained bounds but mounts fresh content. Applications own unsaved-change handling.
Set
closable: falseto omit the close action. minWidth/minHeightdefault to 240 / 160px. The workspace bounds take precedence when the viewport is smaller. Resizing the workspace brings all normal and restore bounds back inside; expanding it does not recover discarded geometry.defaultLayoutrestores aWindowManagerState;onLayoutChangereports initial state and subsequent changes. The application owns validation and persistence. Use unique, stable IDs and memoize window definitions when possible.useWindowManagerexposes state and actions for custom shells;AppWindowis also available as a controlled component inside a positioned parent. Supply the parent's dimensions asareawhen using it directly.
Workspace switching, a production taskbar/Start menu, and automatic storage are intentionally left for the next layer. Windows are non-modal and do not trap focus; embedded cross-origin frames own their internal focus behavior.
FairShare apps
@fairshare/ui-kit/app is the client side of a FairShare app whose server
uses carbon. It covers:
- invite-only sign-in with Identity Platform, using the settings at
/api/config; - the API client, which sends the ID token, refreshes it once when it's rejected, and signs out if it's rejected again;
- page helpers.
It needs @firebase/app and @firebase/auth, which are optional peer
dependencies: apps that only use components don't need them.
import '@fairshare/ui-kit/styles.css'
import '@fairshare/ui-kit/app.css' // page layout: .shell, .page, .form, ...
import { start } from '@fairshare/ui-kit/app'
start({ name: 'Neon', noAccount: 'Ask an administrator to invite you.', app: (user) => <Shell user={user} /> })
Pages use api, useGet, ApiError, Page, LoadError, useChange,
when and logout from the same entry. Keep /config, the 401 handling and
the /api prefix in step with carbon.
Theming
Override the package's CSS custom properties globally or on a wrapping element:
:root {
--ui-accent: #6d4aff;
--ui-accent-hover: #5938df;
--ui-radius: 0.5rem;
--ui-font: Inter, system-ui, sans-serif;
}
Dark mode follows the system preference by default. Force a theme with data-theme="dark" or data-theme="light" on an ancestor such as <html>.
Density
For data-heavy screens, set data-density="compact" on <html> (or any ancestor) to halve padding and control heights. Padding and control heights scale, and so do gaps between stacked blocks; icon-to-label gaps stay fixed. Elevated surfaces keep a minimum inset so they stay roomy: Dialog and Drawer padding stops at 0.75x, Popover and Toast at 0.5rem. For a custom density, set --ui-space directly; 1 is the default and 0.5 is compact:
.my-grid {
--ui-space: 0.75;
}
Sizing tokens you can reuse in your own layouts, given as normal / compact: --ui-control-h (32 / 26px, form controls and the default Button), --ui-control-h-sm (24 / 20px, small inline actions; <Button size="sm"> and <Button size="sm" iconOnly>), and --ui-header-h (36 / 28px, minimum height for header bars). Text uses --ui-line-height (20px) and --ui-line-height-sm (16px, for 0.75rem text and smaller), so rows land on whole pixels regardless of your page's line-height.
Popups render into <body>, so set density on <html> if menus, selects, and dialogs should follow it.
Development
npm install
npm test
npm run build
License
MIT