Primitives

The shadcn/ui layer everything else in Flux is built out of: 35 files in src/components/ui/, mostly copied in unchanged and never touched again. That is exactly why they are worth showing. The interesting facts about this layer are not how the buttons look, they are which primitive got rewritten (one), which primitives shipped and are imported by nothing (six), and which capable pieces of machinery the app quietly declined to use.

Controls

10 components

The things a person operates. All ten are stock shadcn/ui wrappers over Radix (or over react-day-picker, in the calendar's case): a cva variant table, a forwardRef, and the project's cn() helper. Flux styles them entirely through its own tokens, so a stock file still comes out violet.

Button
The most-used file in the codebase: 35 other files import it. Six variants crossed with four sizes, plus an asChild escape hatch that renders the styling onto whatever child you pass, which is how every link-that-looks-like-a-button in Flux works.
src/components/ui/button.tsx
Six variants · default size, h-10
Four sizes · default 40px, sm 36px, lg 44px, icon 40x40
The same four sizes, outline
With an icon · and disabled

Stock shadcn/ui, unmodified. Every icon inside a button is forced to 16px by the base class [&_svg]:size-4, which is why no caller ever sets an icon size. The disabled look is disabled:opacity-50 plus disabled:pointer-events-none, so a disabled button is not dimmed-but-clickable, it is inert.

Input
A single-line text field, 40px tall, imported by 20 files. It is a plain <input> with classes: no label wiring, no error slot, no validation. Everything Flux knows about a field lives in the caller.
src/components/ui/input.tsx
Default, filled, and disabled

Stock. One detail that matters for this showcase: the text size is text-base md:text-sm, so it is 16px on a phone and 14px from the medium breakpoint up. That 16px is not a style choice, it is what stops iOS Safari zooming the page when the field is focused. Every card on this page renders at desktop width, so you are seeing the 14px branch.

Textarea
The multi-line field, minimum 80px tall. 17 files import it, which makes it the surface most of Flux's actual writing happens in: notes, journal entries, friction descriptions.
src/components/ui/textarea.tsx
Empty · placeholder
Filled

Stock, with one cosmetic difference from its neighbours: this is the only file in ui/ written with single quotes and no space inside the import braces. It was clearly formatted by a different tool than the other 34. Nothing renders differently.

Label
A Radix label that ties itself to a control via htmlFor, so clicking the word focuses the field. 28 files import it, second only to Button.
src/components/ui/label.tsx
Above a field · and beside a switch

Stock. It carries a cva call with no variants at all, which is shadcn's own template artefact and does nothing. Its one real behaviour is the pair peer-disabled:cursor-not-allowed peer-disabled:opacity-70: a label dims itself when the control it is a sibling of is disabled.

Checkbox
A 16px square that fills with primary when checked. Nine files import it, mostly for lists of things you tick off.
src/components/ui/checkbox.tsx
Unchecked, checked, disabled

Stock. The border is border-primary rather than border-input, so an unchecked checkbox is already violet-outlined and reads as interactive before you touch it. That is shadcn's default and Flux kept it.

RadioGroup
Pick exactly one. Only two files import it, and both use it for a settings union where the options are mutually exclusive by definition.
src/components/ui/radio-group.tsx
One selected, two not

Stock. The group itself is grid gap-2, so radios stack with an 8px gutter unless the caller overrides it. Both callers in Flux do override it, which is a small sign that the default spacing was never quite right for this app.

Switch
An immediate on/off. Nine files import it, and the distinction from Checkbox is enforced by convention only: Flux uses Switch where the change applies at once and Checkbox where it applies on save.
src/components/ui/switch.tsx
Off, on, disabled

Stock. Off is bg-input, the same grey as a text field's border, so an off switch and an empty field are deliberately the same colour. The thumb travels exactly 20px (translate-x-5) inside a 44px track.

Slider
A single-thumb range control. Three files import it; the one that matters is the signal rating on an entry, where the number you land on is written back as a training correction.
src/components/ui/slider.tsx
At 0, at 40, at 100

Stock, and it renders a single thumb because Radix emits one thumb per value in the array and this file hardcodes exactly one SliderPrimitive.Thumb. A two-handle range is therefore not available in Flux without editing this file.

Select
A dropdown that picks one value from a short list. Nine files import it. The trigger is the same 40px height as Input on purpose, so a form row of mixed fields lines up.
src/components/ui/select.tsx
Closed trigger · with value, and with placeholder
Atomic
Select a priority
Open · content with the selected item ticked
Atomic
Atomic
Container

Stock. Items are padded pl-8 to reserve room for the check mark whether or not they are selected, which is why an unselected item's text still starts 32px in. The chevron sits at opacity-50.

Calendar
A month grid for picking a date. The only primitive here not built on Radix: it wraps react-day-picker and restyles every one of its class slots. Exactly one file imports it, the date picker in the app header.
src/components/ui/calendar.tsx
Month view · selected, today, outside days
March 2025
MoTuWeThFrSaSu
242526272812
3456789
10111213141516
17181920212223

Stock, including showOutsideDays = true, which is why the greyed-out tail of the previous month is shown. The nav arrows are not their own component: they reuse buttonVariants({ variant: "outline" }) and then shrink it to h-7 w-7, so they are a Button's styling on a day-picker's element.

Surfaces and layout

9 components

The things that hold other things: panels, overlays and dividers. Flux leans hard on this group. Card alone is imported by 32 files, which makes it the shape of nearly every screen in the product.

Card
Six exported pieces that compose into one panel: Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter. Nothing is required, so a card can be a titled panel, a bare box, or a stat tile with a description and nothing else.
src/components/ui/card.tsx
The full set · Header, Title, Description, Content, Footer
Access requests
People who requested access.

Content sits at p-6 with the top padding removed, so it hangs directly off the header without a double gutter.

Header only · the stat-tile shape
Entries
248

Stock. CardTitle defaults to text-2xl, which is 24px and far too large for most of what Flux puts in a card, so almost every caller overrides it. The stat tile above is the pattern the admin Stats tab uses: Description on top, Title underneath, no content at all, and the visual hierarchy inverted by size rather than by order.

Dialog
A centred modal over a dimmed page. 12 files import it. It always renders its own close X in the corner, so callers never add one.
src/components/ui/dialog.tsx
Content · header, body, footer
Delete this entry?
It will disappear from today but stay visible in the locked plan.

Stock. Its overlay is bg-black/80, which is a much heavier dim than most light themes use. The panel itself is bg-background (#F5F5F5), not bg-card, so a Flux dialog is very slightly grey rather than white, and a Card placed inside one is visible as a lighter rectangle.

Sheet
A panel that slides in from an edge. Same Radix dialog underneath as Dialog, different geometry. Three files import it, and it is what makes Quick Add and the entry details panel feel like drawers rather than modals.
src/components/ui/sheet.tsx
Four sides · the panel in grey, its overlay in black
bottom
top
left
right (default)
Content · header, title, description
Quick Add
Capture something without leaving the page.

Stock. Left and right sheets are w-3/4 capped at sm:max-w-sm (384px), so on a phone a side sheet covers three quarters of the screen and on a desktop it is a fixed narrow column. Top and bottom sheets have no height cap at all: they are as tall as their content, which is why the bottom sheet is the one Flux uses on mobile.

Popover
A small anchored panel that is not modal: the page behind stays live. Four files import it, including the conflict resolver on the calendar grid.
src/components/ui/popover.tsx
Open · anchored under its trigger
Resolve Conflict

Stock. Fixed at w-72 (288px) unless overridden, and offset 4px from its trigger. Reproduced here at that exact width so the wrapping behaviour is the product's, not this page's.

Tooltip
A hover label. Four files import it, and in Flux it does most of its work in the collapsed sidebar, where it is the only thing telling you what an icon means.
src/components/ui/tooltip.tsx
Beside a collapsed icon button
Calendar

Stock. It ships no arrow: Radix offers TooltipArrow and this file does not export it, so no tooltip anywhere in Flux has a pointer connecting it to its trigger. Position is carried by proximity alone.

DropdownMenu
The overflow menu behind every three-dot button. Nine files import it. It exports 15 pieces including checkbox items, radio items, sub-menus and a shortcut hint slot, most of which Flux never uses.
src/components/ui/dropdown-menu.tsx
Open · label, items, separator, destructive item
Entry
Edit
Duplicate
Locked
Delete

Stock. There is no destructive variant in the file: the red item above is a caller passing className="text-destructive", which is how every dangerous menu item in Flux is coloured. The highlighted row is focus:bg-accent, and because Flux's --accent is a saturated teal, a keyboard-focused menu item goes teal-on-white rather than grey.

Accordion
Stacked sections that open one at a time. Four files import it, mostly for advanced options that should be present but not first.
src/components/ui/accordion.tsx
One open, one closed
Priority, container, and the flexible-length toggle.

Stock, including hover:underline on the trigger, which is the one shadcn default that reads oddly here: hovering a section heading underlines it as if it were a link. The chevron rotates 180 degrees via [&[data-state=open]>svg]:rotate-180.

Collapsible
Accordion's unstyled cousin: one section that opens and closes, with no borders, no chevron and no opinion. One file imports it.
src/components/ui/collapsible.tsx
Open · entirely caller-styled
Everything this component contributes is the open/closed state. The look above is the caller's.

Stock, and the shortest file in ui/ at 11 lines: three re-exports of the Radix primitive with no cn() and no classes at all. It is here because it is one of the 35, not because it has a look of its own.

Separator
A one-pixel rule, horizontal or vertical. Seven files import it. It defaults to decorative, which means screen readers skip it rather than announcing a separator.
src/components/ui/separator.tsx
Horizontal and vertical
Time
Variables
Planned Actual

Stock. Vertical separators are h-full, so one only appears if its parent has a height. Drop it into a plain div and it collapses to nothing, silently, which is the most common way this primitive appears to be broken.

Feedback and state

7 components

How the app tells you something: that it worked, that it did not, that it is still thinking. Between them Badge and Toast carry almost every status signal in Flux, and because both offer a destructive variant, red is the app's whole vocabulary for "bad".

Alert
An inline banner that stays on the page, as opposed to a toast that leaves. Two variants, three files import it.
src/components/ui/alert.tsx
Default
Plan not locked
Lock the plan for this day to compare it against what happened.
Destructive
Time Conflict
This task is outside its container's time window.

Stock. There are only two variants: no success, no warning, no info. Anything Flux wants to say that is neither neutral nor a failure has to be a default alert with a chosen icon, which is why the neutral one above carries an info glyph the component itself knows nothing about.

Badge
A pill of status text. 25 files import it, which makes it the app's main way of saying what state something is in. Four variants, and every status union in Flux is squeezed into them.
src/components/ui/badge.tsx
Four variants
default secondary destructive outline
As the app uses them · a three-way status mapped onto three variants
approved waiting rejected

Stock. Note what the variant list is missing: there is no success variant, so "approved" and "primary action" are the same violet. The outline variant sets only text-foreground and relies on the base class's border, so it is the one variant whose colour comes from the border token rather than from a fill.

Toast
The transient message that slides in and leaves. Two variants. This file is the parts bin; the thing that actually renders toasts is Toaster, next door.
src/components/ui/toast.tsx
Default
Signup mode updated
invite_only
Destructive
Failed to update signup mode
The change was rolled back.

Stock. Padding is p-6 pr-8, which is 24px all round and 32px on the right to clear the close button, so a two-word toast is still 72px tall. The close X is opacity-0 until the toast is hovered or focused: it is shown here so you can see it exists.

Toaster
The one component that mounts the toast queue. Imported exactly once, in the root layout, and never thought about again. Every toast() call anywhere in Flux surfaces through this.
src/components/ui/toaster.tsx
Two queued · newest on top, viewport bottom-right on desktop
Copied
FLUX-2K4M
Invite code created
FLUX-2K4M

The queue is one toast deep. The stack above is what the viewport's layout would do, but the app's toast hook caps the list at a single entry, so a second toast() replaces the first rather than stacking under it. Padding is reduced here to fit two in a card; the real toast is p-6, as shown on the Toast card.

Progress
A determinate bar. Two files import it. It has no indeterminate mode, so anything Flux cannot put a percentage on has to use a skeleton or a spinner instead.
src/components/ui/progress.tsx
At 0, 45 and 100

Stock, and worth reading closely: the fill is a full-width bar translated left by 100 - value percent, not a bar whose width is set. At 0 it is still there, parked entirely outside the track. That is also why a missing or undefined value renders as 0 rather than as an error.

Skeleton
A pulsing grey block that stands in for content that has not arrived. Three files import it. It has no props: the caller supplies the shape entirely through className.
src/components/ui/skeleton.tsx
Loading a list

Stock, 15 lines, and the only file in ui/ that imports neither React nor Radix. Its animation is animate-pulse, which dips to 50% opacity and back on a 2 second cycle. Reproduced faithfully here, including honouring reduced-motion, which the product does automatically through Tailwind's own media query handling.

Avatar
A 40px circle with an image and a fallback. Three files import it, all in the parts of Flux that model people rather than time.
src/components/ui/avatar.tsx
Fallback initials · the state Flux is almost always in
PR TA

Stock. AvatarFallback sets only bg-muted: no text size, no weight, no colour. Every caller therefore has to style its own initials, and the fallback is what people actually see, because Flux stores no avatar images. The image branch exists and is effectively unreachable.

Data and navigation

2 components

Two primitives for showing rows and for switching between panels. Both are stock, and both are used sparingly: Flux is a calendar, so most of its data is positioned rather than listed.

Table
Eight exported pieces wrapping a real <table>. Only two files import it, and both are in the admin panel, which makes Table the most admin-specific primitive in the set.
src/components/ui/table.tsx
Header, rows, and a right-aligned action column
CodeUsesStatusActions
FLUX-2K4M0 / 1 active
FLUX-8QRT1 / 1 exhausted
Empty · there is no empty state in the component
CodeUsesStatusActions

The empty variant is the finding. TableBody with no rows renders the header and then stops: no message, no placeholder row. Every caller that wants an empty state has to check the array length itself and render something else instead of the table. Rows are shown compact here to fit the card; the real header is 48px and cells are 16px-padded, as in ui/table.tsx.

Tabs
A segmented control with panels behind it. Six files import it, including the admin panel, which is nothing but a TabsList and five TabsContent blocks.
src/components/ui/tabs.tsx
List with the first trigger active

Stock. The active trigger is data-[state=active]:bg-background, and since TabsList itself is bg-muted, the selected tab reads as a raised chip in a grey trough. The list is inline-flex with no wrapping and no overflow handling, so enough tabs will push it past its container rather than scroll.

The one that was customised

1 component

Of the 35 primitives, 34 are stock shadcn/ui. This is the exception, and it is a big one: 804 lines, the largest file in ui/ by a factor of two, and the only primitive that reaches back into the application it is supposed to know nothing about.

Sidebar
The collapsible navigation rail, and the only file in the primitives layer that is not a generic component. It imports the app's settings context, which means this "primitive" cannot be lifted out and dropped into another project without cutting a dependency first.
src/components/ui/sidebar.tsx
Expanded · 16rem
Collapsed · 3.25rem, not the stock 3rem
The setting that drives it · a 4-way union in the app's settings context
The default. Collapsing switches the rail to icons.
Pointer in expands it, pointer out collapses it again.
Collapsing takes the rail off-canvas entirely, width zero.
Collapsing goes to the icon rail.
Mobile · the rail becomes a Sheet with a real header
Menu
Navigate through the application.
The only keyboard shortcut in the app
Ctrl + B toggles the sidebar. That is the complete list.

What actually diverges from stock, line by line. It imports useSettings() from the app's settings context, a coupling stock shadcn's sidebar does not have. It adds handleMouseEnter / handleMouseLeave to implement "open on hover". It computes an effectiveCollapsible that overrides the collapsible prop the caller passed, deriving it from the 4-way sidebarDisplayMode instead. SIDEBAR_WIDTH_ICON is "3.25rem" where stock is 3rem. And the mobile SheetContent gains a real SheetHeader with the title "Menu" and the description "Navigate through the application.", which stock leaves as a screen-reader-only stub. The same SheetContent is passed [&>button]:hidden, which suppresses the Sheet's own close X, so the mobile rail is dismissed by tapping outside it or by pressing Escape and by nothing else.

One more thing worth stating plainly. SIDEBAR_KEYBOARD_SHORTCUT = "b" makes Cmd/Ctrl+B toggle the rail, and that window.addEventListener("keydown", ...) is the only global key handler in the entire codebase. There is no command palette, no shortcut sheet, nothing else to learn. A 35-file primitives layer, and the app's whole keyboard surface lives in one of them.

Shipped but unused

6 components

Six primitives are in the bundle's source tree and imported by zero files. They arrived with the shadcn scaffold and were never reached for. Two of them are more interesting than the other four, because the app did not skip the job they do: it did the job by hand instead.

Chart Unused
A 365-line Recharts theming wrapper: ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent and ChartStyle, plus a light/dark THEMES map that injects per-series CSS variables. Nothing imports it.
src/components/ui/chart.tsx
Not rendered
Nothing imports this file.

Flux draws four charts. Every one of them bypasses this file and uses raw Recharts directly. The four are the two circular views, the insights summary report, and the time analysis view; all four import from recharts and none import from ui/chart. So the tooltip styling, the legend styling and the theme variable injection this file provides are all rebuilt by hand at each call site, or not at all.

Form Unused
The react-hook-form binding layer: Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage and the useFormField hook that wires ids, aria-describedby and validation messages together. Nothing imports it.
src/components/ui/form.tsx
Not rendered
Nothing imports this file.

Every form in Flux is hand-rolled useState instead. This file is the only place in the whole of src/ that imports react-hook-form, and since nothing imports this file, the library is in the dependency tree and reaches no user. The practical consequence is that the accessibility wiring it exists to provide, the label-to-input id binding and the aria-describedby pointing at the error message, is absent from every field in the product.

AlertDialog Unused
The confirm-before-you-destroy modal, with Action and Cancel buttons built in and no dismiss-by-clicking-away. Nothing imports it. Flux's confirmations use the ordinary Dialog instead.
src/components/ui/alert-dialog.tsx
Not rendered
Nothing imports this file.

The difference this would have made: an AlertDialog cannot be dismissed with Escape or an outside click, so a destructive choice has to be answered. Flux's delete confirmations are Dialogs, which can be waved away.

Menubar Unused
A desktop-application menu bar: File, Edit, View, with sub-menus, checkbox items, radio items and shortcut hints. 256 lines, 16 exports, zero importers.
src/components/ui/menubar.tsx
Not rendered
Nothing imports this file.

It exports a MenubarShortcut slot for rendering key hints beside menu items. Flux has exactly one keyboard shortcut and it is not in a menu, so the slot has nothing to display even if the bar were mounted.

ScrollArea Unused
A Radix scroll container with a styled custom scrollbar, so a scrolling panel looks the same on every platform. Nothing imports it: everywhere Flux needs to scroll, it uses a plain overflow-auto and the operating system's own bar.
src/components/ui/scroll-area.tsx
Not rendered
Nothing imports this file.

This is the one whose absence you can see in the product: the calendar, the inbox and the admin tables all scroll with the native bar, which on Windows is a wide grey gutter and on macOS is an overlay that fades. The component that would have made those identical is sitting right here, unimported.