Shipbytes design system · v0.1

Every component, every state, in both themes.

Rendered from the actual Blade components rather than drawn to look like them — so if one is broken, this page is broken too. That is the only kind of gallery worth reviewing against.

Specimen theme

Button

Four variants, three sizes. The app writes this eight different ways today; every one of them was trying to be this.

Hover, focus and active are live — interact with them. Disabled and loading are prop-driven and shown below.
Variants · size md
Sizes · sm 32px · md 36px at the sm breakpoint, 44px below it · lg 44px
States
With icons, as a link, and icon-only
A link that looks like a button

Badge

Five tones, three variants. Replaces roughly fifty hand-written badges across nine colour families.

Tint · the default — a badge annotates, it does not act
Draft Admin Verified Unverified Blocked
Solid and outline
Solid Accent Live Failed Outline
With a status dot · decorative — the label already says the state
Published Pending Errored Idle

Icon

Heroicons v2, with the v1 names the app still uses aliased so they resolve instead of rendering nothing.

Sizes · 4 is the dashboard default
3 4 5 6 8
Variants
outline 24 solid 24 mini 20 micro 16
v1 names still resolve · v2 renamed 33 of the names this app uses
x → x-mark search → magnifying-glass mail → envelope cog → cog-6-tooth dots-vertical → ellipsis-vertical location-marker → map-pin

Input

A Catalyst two-layer field. In the app this is a ~40-line class string pasted at roughly forty sites.

Focus the fields. The ring is drawn inward, so the box never changes size and nothing below it shifts — a plain outline would.
States

Shown on the dashboard card and in the shared link.

Name must be at least 3 characters.

Types · the select shares the shell exactly; only the caret differs
Help is replaced by the error · never stacked — two lines of guidance under one field is one too many

We only use this for password resets.

That does not look like an email address.

Alert

Fifteen hand-written flash blocks across twelve files, all trying to be these four.

A wash, not a tint. The same hue needs more saturation to register in a badge than across a whole alert — badge-strength fill at panel size reads as shouting.
Tones · danger announces assertively; the rest do not interrupt

Export ready

Your file is ready and stays available for 24 hours.

Your plan expires in 3 days

Renew to keep unlimited exports. Existing projects stay editable either way.
Template switching is always free — it never costs credits.
Wash against tint · the same tone at two surface sizes
A tint, at badge size
A wash, at alert size

Stat tile

Fifteen instances across the dashboard and admin, with the count-up duplicated verbatim five times in one file.

The count-up is skipped when you have asked for reduced motion — the hand-written version animated regardless.
The row · interactive tiles carry a hover state; the third does not, because it goes nowhere

Panel

A bordered container with an optional header and a divided body. Four copies on the dashboard alone.

The title wraps and the action does not — truncating a title hides what the reader came for, and a wrapped “View all” costs a line and looks broken.
Feature · a destination, not a sibling — larger radius, solid edge, icon tile

Needs attention

Based on your preferences

View all
V Staff Designer Vercel · Remote N Design Lead Notion · London

Table

Twelve admin screens share one recipe. Columns are passed as data, not written as markup.

The scroll container is not optional — without it one long cell makes the whole page scroll sideways, which on a phone looks like a broken layout.
Rows, alignment and an actions column
User Role Status Projects Joined
AR Amara Reyes [email protected] Admin Verified 14 Mar 04, 2026
TO Tobias Okonkwo [email protected] User Unverified 2 Aug 19, 2026
SK Sunniva Kall [email protected] User Blocked 7 Jan 22, 2026
Empty · the header stays — the reader needs to know what is empty
User Role Joined

No users found.

Sheet

The mobile form of a modal or drawer. In the app it is the “More” menu — the largest single block in the shell.

Tile colours are a pass-through, not a token: these are decorative wayfinding, and five semantic tones do not stretch to ten features without lying about meaning.
Rows · shown flat; the real sheet slides up over a scrim

More Services

Avatar

An image when there is one, initials when there is not. Round is a person; square is a company, a project or a file.

Sizes · sm is 32px, level with a small button in a table row
AL AL AL AL AH
Initials come from the FIRST and LAST word — Ada King Lovelace is AL, because the middle name is the part nobody uses.

Empty state

Most emptiness is the beginning, not a failure. The dashed edge says something belongs here and does not exist yet.

Nothing yet · neutral — nothing has gone wrong

No reports yet

Reports appear here once a project has run for a full day.

With a cause · spend a tone only when the emptiness HAS one

Nothing matched

Try a broader date range.

Import failed

The file could not be read.

Skeleton

A picture of content that does not exist yet. The fill is fg/10 so one value works on the card, on the ground behind it, and in dark.

The last bar is short. A stack of equal bars reads as a table; a paragraph ends mid-line, and that detail is most of what makes this look like text.
Text, block and circle

Checkbox & radio

The native input, styled with appearance-none — not a hidden input beside a decorated span. The trick loses high-contrast rendering and forced-colors support, and every one of those failures is invisible in a normal browser.

Checkbox · the label is a real <label for>, so its whole width is the touch target

At most one a month.

You must agree to continue.

Radio · a dot, not a tick — the shape is the difference between “one of these” and “any of these”
Billing period

You can change this later.

Two months free.

Tabs

The active underline is fg, not accent. A tab row is structure, not a link — colouring the active one accent makes the inactive tabs look like the links.

navigation is not a styling flag, it is the accessibility contract. role="tablist" promises arrow keys and in-place content; put it on page links and both halves are false.
Panels · role=tablist — buttons that swap content on this page
Panels are hidden with the hidden ATTRIBUTE, so an inactive one leaves the tab order for free.
Navigation · <nav> and aria-current — these are links to other pages

Select

A listbox, for where the native popup’s unstyleable chrome is the problem. The native one is still right on anything phone-first — it gets typeahead and the platform’s own scrolling for free.

The gallery carries no JavaScript, so every list renders open — and the ticks and the trigger label are still right, because the component renders its selected state in PHP rather than waiting for Alpine.
Open · the selected option carries a tick AND the weight change — weight alone is not reliable when comparing two rows
  • Free
  • Pro
  • Team
  • Enterprise

You can change this later.

States · the trigger shares the input’s geometry exactly, so the two line up on one row
  • Europe
  • GBP

Pick a currency.

Tooltip

Supplementary, never the only copy. It does not exist on a touch screen, in a printout, or once the pointer moves — so the button keeps its own accessible name and the tip only describes it.

Placements · shown open; in the app they appear on hover AND on focus
Above Below Delete this report permanently

Toast

Surface with a border, not the alert’s tinted wash: it floats over unknown content and has to paint an opaque ground. The tone lives in the icon alone, so four stacked toasts do not become a colour chart.

The region is rendered always, and empty most of the time. A live region only announces content that arrives after it is in the document — render it with the first toast and the first toast is silent.
Stacked · bottom-right

Report exported

The CSV is in your downloads.

Export failed

Try a smaller date range.

Report archived

Draft saved.

Drawer

The third blocking overlay. A modal is answered; a drawer is worked in. The tell is how long the reader stays.

From the right · filters — the body scrolls, the footer keeps Apply reachable
Every width · sm 24rem · md 28 · lg 32 · xl 36 · 2xl 42 · full = calc(100vw − 3rem)
sm
md
lg
xl
2xl
full
full is calc(100vw − 3rem), not max-w-none. The container is fixed inset-0 with no padding, so edge-to-edge would leave nothing of the page visible — and a panel covering everything is a screen, not a drawer. The sliver is what says the thing you came from is one click away.
Wide · size 2xl · a drawer used to READ a record rather than filter one
From the left · navigation lives on the left, so its drawer does too

Switch

A setting that takes effect. role="switch" makes a screen reader say “on” and “off” — “unchecked”, for a setting that is simply off, reads as a form the reader failed to fill in.

The label sits left and the control right, the opposite of the checkbox: a switch is one of a list of settings the reader scans down the left edge.
States · an unchecked checkbox posts NOTHING, so a hidden 0 rides alongside

At most one a month.

Things that may change without warning.

Accordion

Sections that expand one at a time. The one component in the system that holds state — exclusivity is a relationship between items, so no single item can enforce it.

A collapsed panel is visibility: hidden, not overflow: hidden. That takes its content out of the tab order AND the accessibility tree; a collapsed section whose links are still tabbable is the classic broken accordion, and it looks perfect.
Exclusive · the open section renders open, before Alpine boots

Your card ending 4242 is charged on the first of each month.

Combobox

A select with a text filter. multiple is a mode of it, not a second component — chips and aria-multiselectable are the whole difference.

Chips sit inside the field. A list of choices below the control reads as results, and people click them expecting to select rather than to remove.
Multiple · shown open; the gallery carries no JavaScript
  • No matches

Start typing to filter.

Date picker

A calendar for one date or a range. Every value is a Y-m-d string, never a Date — new Date('2026-03-29') is UTC midnight, which west of Greenwich is the 28th.

A range posts two fields. Two dates are two values, and squeezing them into one string means every consumer writing the same parser.
Range · endpoints inverse, the span washed — shown open

File upload

A styled file field with drag-and-drop and local previews. It does not upload anything — progress needs somewhere to upload to, and that is a backend contract nothing else here has.

The real <input type="file"> is stretched over the whole zone at zero opacity, so the entire area is the control and drag-and-drop lands on the element that owns the files.
Drop zone · the accept string is rendered for a person: image/*,.pdf becomes “images, PDF”

Choose files or drag them here

images, PDF · up to 5 MB

Together

The components on a real surface, so spacing and weight can be judged in context rather than in isolation.

A resource-list header and a row

Projects

19 documents

Senior Product Designer Modern · 2 hours ago 92
Design Engineer 2026 Classic · yesterday 74
Untitled project Not started · 3 days ago Draft
A destructive confirm

Delete this project?

“Senior Product Designer” moves to trash. You can restore it for 30 days.

Tokens

Every token names a job, not a colour. Both themes are shown side by side because that is the only way drift between them is visible.

surface the card content sits on
surface-sunken the ground behind it
surface-subtle table heads, row hover
surface-inverse primary buttons, the FAB
border the default hairline
border-strong a card edge that must read as raised
fg headings
fg-body running copy
fg-muted labels, meta, timestamps
fg-subtle icons that repeat an adjacent label
accent links
success success
warning warning
danger danger
fg-subtle is 2.63:1 on the card in light — below the 3:1 a meaningful mark needs. It is allowed only where the information is also written next to it. Timestamps are not redundant, so they use fg-muted.
display 30/32 · 600 The quick brown fox jumps
heading 20/32 · 600 The quick brown fox jumps
section 14/20 · 600 The quick brown fox jumps
body 14/24 · 400 The quick brown fox jumps
body-touch 16/24 · 400 The quick brown fox jumps
meta 12/16 · 400 The quick brown fox jumps
overline 12/16 · 500 The quick brown fox jumps