v0.1 One stylesheet No build step 0 dependencies

A CSS framework you add with one link tag.

Deck is a single stylesheet: buttons, forms, tables, a data grid, charts, overlays, an icon sprite, and a full color system. There is no build step, no config file, no purge pass, and nothing to install. It ships in cascade layers, so your own CSS wins without !important, and every color on the page derives from one number you can change at runtime.

Get started

Those eleven steps, every button, link, focus ring, badge, chart series, and shadow on this page are computed from --hue-brand. Drag the slider in the header and watch all of it retune, live, with no rebuild.


What is Deck?

Deck is a CSS framework that ships as a single 26.8 KB stylesheet containing buttons, forms, tables, a data grid, charts, overlays, an icon sprite, and a complete color system. You add it to a page with one <link> tag, and it has no build step, no configuration file, and 0 runtime dependencies.

Deck is written as 26 plain CSS files that are concatenated into deck.css, defining 927 classes. It is published under the MIT license, and version 0.1.3 is the release documented on this page.

What does Deck actually weigh?

A page that loads the stylesheet, the icon sprite, and the optional JavaScript transfers 62.8 KB Brotli, or 76.3 KB gzip. Every browser Deck supports sends br in Accept-Encoding, and Cloudflare, Vercel, Netlify, and nginx with ngx_brotli negotiate it for text by default, so Brotli is what most users receive.

Transfer size of each Deck file, Brotli and gzip
File Brotli gzip What it is
deck.min.css 26.8 KB 32.7 KB The whole framework. Fixed size.
deck-icons.svg 27.1 KB 33.6 KB 75 icons at two weights, 152 symbols. Only if you use the icons.
deck.min.js 8.9 KB 10.0 KB Optional. Only for components that need behaviour.
All three 62.8 KB 76.3 KB The honest total for a page that uses everything.

The sprite is the largest single file, slightly bigger than the stylesheet, which is worth saying plainly rather than leaving for you to find in devtools. Swapping deck.min.js for the full deck.bundle.min.js, which adds the date picker, combobox, data grid, toasts, and QR encoder, makes the JavaScript 17.3 KB and the total 71.2 KB Brotli (85.8 KB gzip). Every figure here is measured by npm run build; the page reads them from dist/sizes.json rather than carrying its own copy.

The sprite is a manifest, not a fixed cost

The 75 icons in the sprite are a default so the demo works out of the box, not a floor you have to pay. tools/icons/icons.txt is a plain list of the icons to extract: delete the lines you do not need, run npm run icons, and the sprite is rebuilt with only what is left.

# keep only the icons you use
$ cat > tools/icons/icons.txt <<'EOF'
check    = check
search   = search
settings = settings
@hand deck-mark
@hand deck-wordmark
EOF

$ npm run icons

A twelve icon sprite measures 5.7 KB Brotli (6.9 KB gzip) — that figure was generated and measured, not estimated. Against 27.1 KB for the full set, trimming the manifest to what a project actually uses is the difference between the sprite dominating the page weight and disappearing into it.

This matters because an external sprite is all or nothing per request: the browser fetches the whole file to resolve one <use>, so an unused icon is not free the way an unused CSS class is. That is the reason the manifest exists. The two @hand lines carry the brand marks through from the previous sprite; drop them and the marks are dropped too.

Do I need a build step to use Deck?

No — Deck needs no build step, because it is distributed as a finished stylesheet with no compiler, no bundler plugin, no PostCSS pipeline, and no purge pass between you and a styled page. The file you download is the file the browser reads, which means a Deck project has nothing to rebuild when you change a color, and nothing to reinstall when you clone it onto a new machine.

How do I install Deck?

Add one <link> tag pointing at deck.css and the framework is installed. The JavaScript file is optional and only needed for components that require behaviour, such as the date picker, the combobox, and the toast queue; every other component works as pure CSS with the script absent.

<link rel="stylesheet" href="/assets/deck/deck.css">
<script src="/assets/deck/deck.js" defer></script>  <!-- optional -->

Deck is also on npm as @echodial/deck and on Packagist as echodial/deck, and the npx @echodial/deck init public/assets/deck command copies the files into a project without installing anything permanently.

How do I change the color scheme?

Set --hue-brand to a number between 0 and 360 and every brand color in the framework is recomputed from it. Deck derives its whole palette from six hue values in oklch, so a theme change is one custom property rather than a rebuild, a second stylesheet, or a set of overrides.

:root { --hue-brand: 265; }        /* violet instead of teal */
<html style="--hue-brand: 320">   /* or per tenant, at runtime */

What browsers does Deck support?

Deck targets Chrome 117, Edge 117, Safari 17.4, and Firefox 128 and newer, which are the versions that shipped cascade layers, container queries, oklch(), and the popover attribute. Those features are load-bearing rather than progressive enhancements, so Deck does not attempt to support browsers released before them.

Is Deck free to use commercially?

Yes — Deck is MIT licensed, which permits commercial use, modification, and redistribution provided the copyright notice is kept. The bundled icon outlines are derived from Google's Material Symbols and carry the Apache License 2.0, whose notice is reproduced in the sprite's header comment.


How is Deck different from Tailwind CSS and Bootstrap?

Deck differs from Tailwind CSS and Bootstrap in that it has no build step and no configuration file, and its entire palette can be rethemed at runtime by changing one CSS custom property. Tailwind generates a stylesheet from your markup at build time, and Bootstrap needs a Sass compile to customise beyond its CSS variables; Deck is a fixed file that you link and then override with ordinary CSS.

Deck compared with Tailwind CSS and Bootstrap
QuestionDeckTailwind CSSBootstrap
Build step None Required — the stylesheet is generated from your markup Not for the prebuilt CSS; required to customise via Sass
Config file None tailwind.config.js, or a CSS @theme block in v4 Sass variables
Runtime dependencies 0 0 in the output CSS; a Node toolchain to produce it 0 for the CSS; Popper for dropdown and tooltip JavaScript
CSS size, compressed 26.8 KB Brotli, fixed Varies with how many utilities you use Fixed; see their release notes for the current figure
Retheme without rebuilding Yes — one custom property, at runtime Partly — v4 exposes CSS variables; config changes need a rebuild Partly — v5.3 exposes CSS variables; Sass changes need a recompile
Right-to-left Built in — set dir="rtl", no second file Logical-property utilities; a plugin for full coverage A separate right-to-left stylesheet build
Overriding the framework Cascade layers — unlayered CSS always wins Utility order and !important where needed Specificity, or !important
Ecosystem and plugins 19 first-party templates; no plugins Large — component kits, plugins, templates Large — themes, plugins, long-standing community
Editor tooling Deck IntelliSense for VS Code and editors built on it Official IntelliSense extension Community extensions and snippets
Maturity Version 0.1.3, one author Established, funded, large team Established since 2011, large team

Where Deck loses

Deck has no plugin marketplace, no third-party component kits, editor support only in Visual Studio Code and the editors built on it, and one maintainer, so a team that needs a large hiring pool or a deep catalogue of ready-made themes is better served by Tailwind CSS or Bootstrap. Deck also has a fixed stylesheet size: a page that uses six components downloads the same 26.8 KB as a page that uses all of them, whereas Tailwind's generated output scales down with usage.

Deck's figures above are measured from this repository with npm run build. The Tailwind CSS and Bootstrap columns describe documented behaviour rather than measurements taken here; both projects change between releases, so check their current documentation before relying on a number.


Buttons

Seven variants, three sizes, groups, and a loading state.


Cards and stats

Orders this month 1,284 12.4% vs last month
Average time to ship 3.2 days 0.6 days faster
PL
Priya Lakhani
Engineering manager

Opened eleven pull requests this week. Two are blocked on a review from the platform team.

9 merged 2 blocked
Scheduled maintenance

The API will be read only on Sunday from 02:00 to 04:00 UTC.

Deploy succeeded

Build 4471 is live on production. 42 seconds, no errors.

Storage is nearly full

You are using 47 GB of your 50 GB plan.

Payment failed

The card ending 4242 was declined. Update it to keep the plan active.


Forms

Inputs are 16px on touch devices, so iOS never zooms when a field takes focus. Every control clears a 44px target.

Shown in the header and on invoices.
Enter an address in the form name@example.com
$ per month
This box grows as you type.
Email me when
Plan

Tables

Below 640px this table restacks into labelled rows instead of forcing a sideways scroll. Resize the window to see it.

OrderCustomerStatusTotalAge
#1042 Northwind Traders Shipped $842.16 2d
#1041 Globex Corp Packing $318.00 5d
#1039 Initech Refunded $0.00 11d

Navigation and disclosure

How do I invite someone to a workspace?
Settings, then Members, then Invite. They get an email with a link that expires after seven days.
Can I change my plan mid-cycle?
Yes. Upgrades take effect immediately and we prorate the difference. Downgrades apply at the start of the next billing period.
Where do I find my API key?
Settings, then Developers. Keys are shown once at creation time, so store it somewhere safe before you close the dialog.
Order placed
Mar 3, 8:14 AM
Handed to the carrier
Mar 3, 2:40 PM
Delivery
Expected Mar 12

Icons and emoji

Deck ships 75 icons as a single SVG sprite of 152 symbols — each icon at two weights, plus two brand marks, 27.1 KB Brotli — and they inherit color and font size, so they sit on the text baseline without nudging. The sprite is generated from the Material Symbols variable font, so any of its 4,025 icons can be added by putting its name in tools/icons/icons.txt and running npm run icons; only the listed names are extracted, which is how a 4,025 icon library ships as an 75 icon file. Emoji get a pinned font stack so they render the same on Windows, iOS, and Android.

🚀 🎨 📦 Inline ✅ emoji stay on the baseline.
Emoji picker

Overlays

Built on native <dialog> and the popover attribute, so focus trapping, escape-to-close, and the top layer are handled by the browser. The sheet slides up from the bottom on a phone and centers on a desktop.

Hover me

Filter issues

Status
📭 No issues match those filters

Clear a filter or widen the date range to see more.


Date picker

Single date, range with two months, and presets. It's a popover on a desktop and slides up as a sheet under 480px.

Try "Last 30 days" in the rail.
Dates outside the range are struck through and unclickable.

Combobox

Type to filter, arrows to move, enter to pick. The real <select> stays in the DOM and stays in sync, so a plain PHP form post works with nothing extra on the server.

Grouped, with a second line of detail per option.
Multi-select with tokens. Type a new name to add it.

Data grid

Frozen header, pinned first and last columns, sortable headers, resizable columns, and a totals row that sticks to the bottom. Scroll the grid sideways — the shadow only appears once you do.

Order Customer ID Customer Product Rep Status Subtotal Tax Total Placed
#1042 cus_Q4nR8vTk2LpZ Northwind Traders Standard plan, 24 seats Marco Reyes Paid $780.00 $62.16 $842.16 2026-09-03
#1041 cus_H7mE3bXw9Ktf Globex Corp Team plan, 8 seats Ken Spence Pending $295.00 $23.00 $318.00 2026-08-31
#1039 cus_K2pA9zLd4Rye Initech Starter plan, 3 seats Dana Whitfield Refunded $0.00 $0.00 $0.00 2026-08-27
#1038 cus_R5tY6wQn1Bge Umbrella Ltd Enterprise add-on, SSO Priya Lakhani Paid $298.00 $23.40 $321.40 2026-09-05
#1037 cus_B8xC4uMs7Wdq Vandelay Industries Team plan, 6 seats Tina Okafor Pending $278.75 $22.00 $300.75 2026-09-06
5 orders $1,651.75 $130.56 $1,782.31
Showing 5 of 412 Rows per page

Toast queue

Toasts stack instead of stringing down the screen. Hover the stack to fan it out and pause every timer. Swipe or drag one sideways to dismiss it. Fire several in a row to see the queue work.


Charts

No charting library. Bars and donuts are CSS driven by a --value property; lines are inline SVG you style with classes. Every series reads the brand palette, so the hue slider retunes these too.

New signups by month 2026
Weekly active users
MarMayJulSep
This workspace Account average
Sessions by device
412 total
Desktop 62% Mobile 22% Tablet 16%
Top traffic sources
Direct 92
Search 71
Referral 54
Newsletter 38
Social 21
Revenue this week
$18,402
8.1% vs last week
Plan mix
Commits, last five weeks


Transitions and animations

Motion answers an action or shows what changed. Nothing here runs unless you ask for it by class, and everything is off for anyone whose OS asks for reduced motion — except loading indicators, which keep turning slowly, because a frozen spinner reads as broken.

Entrances, staggered
Backlog42 issues
In progress18 issues
Shipped311 issues
Closed41 issues
Scroll reveal

These use animation-timeline: view() — tied to scroll position with no IntersectionObserver. deck.js falls back to an observer where the browser doesn't support it.

Reveal
Fade
Pop
Micro-interactions
Underline sweep Live
Attention and feedback
Pulsing
Order #1042 Order total $842.16
Expand and collapse

A real height: auto transition using interpolate-size. No measuring in JavaScript, no max-height guess.

The connection pool was exhausted because a background job opened a transaction per row instead of per batch. The fix batches at a thousand rows and returns the connection in a finally block.

Ticker
v2.4.0 is rolling out to 12% of traffic Postgres upgrade to 17 scheduled for Sep 19 The legacy /v1 API retires at the end of the quarter Invoices post on Friday
Page transitions without a router

Add @view-transition { navigation: auto; } to your app CSS and full page loads cross-fade like a single page app — no router, no JavaScript, no framework. Deck styles what the browser generates, including holding the header and tab bar still while the content changes.


Cascade layers

Deck declares the whole cascade contract in 00-layers.css before a single rule exists. Order is decided there — not by file order, not by specificity, and never by !important. Four empty app.* layers are reserved for you.

@layer
  deck.reset, deck.tokens, deck.type, deck.layout,
  deck.components, deck.mobile, deck.motion, deck.effects,
  deck.utilities, deck.rtl, deck.print,

  app.base, app.components, app.pages, app.overrides;
Write in a slot

A rule in app.pages beats every Deck rule with a single class selector. No specificity war, no stacked .page .card .btn chains.

Unlayered always wins

Anything outside a layer beats all layers. A one-off rule in a page template overrides Deck with nothing special.

Third-party CSS

Wrap a vendor stylesheet in its own layer with @import url(x.css) layer(vendor) and it stops fighting you.


Container queries

The same card markup, twice. The one in the wide column goes horizontal; the one in the rail stays stacked. Neither knows where it was placed — they respond to the space they were given, not the window.

Designing with container queries

Guides · 8 min read · updated Mar 3

Style queries — set --tone on the container, children follow
All checks passed
Review requested
Build failed

Logical properties

Deck is written in logical properties end to end, so a full right-to-left flip needs nothing but dir="rtl". Try the switch — the layout, the switch knob, the chart fills, the sidebar, and the pointing icons all mirror. The commit hash does not, because an identifier reads left to right in every language.

current: ltr
Mirrors
Coverage 74
Does not mirror

Identifiers stay in their own direction and stay isolated from the text around them.

a81ac26bc87ec010f4d9 glyphs with no direction
Writing modes
Vertical column header UPRIGHT

Gradients

All built from the brand hue and interpolated in oklab, which avoids the grey dead zone you get blending two saturated colors in sRGB. Drag the hue slider and every one of these retunes.

Ship it on Friday

Mesh background, gradient text, no images

Gradient border

Two clip boxes, works with any radius.

Animated border

The angle is a registered @property, so it interpolates.

Shine sweep on text

Clipped to the glyphs, with a real color underneath.

Passing Queued Failing
Grid lines
Dots
Hatch — unavailable slot
Scrim over media

3D transforms

Depth earns its place when it carries meaning: a card with two sides, a pile you are working down through, a control that physically depresses. Everything uses rotate and translate as individual properties, so two effects compose instead of overwriting each other.

Order #1042

Northwind Traders · 24 seats

Breakdown

Subtotal $780.00 · Tax $62.16

Tilt

Move the pointer across this card. The title floats above the face on the Z axis.

PR #418Awaiting review
PR #421Awaiting review
PR #423Awaiting review
PR #427Awaiting review
🚀
📦
🎨
📈
✅
🐛
Coverflow — scroll it sideways
March84 releases
April116 releases
May102 releases
June146 releases
July132 releases

Carousel, drawer, mega menu, speed dial

The carousel is scroll snap underneath, so it swipes correctly with JavaScript off. Arrows and dots are enhancement.

7 All systems normal Degraded

Filter orders

Status
Order total
$250$1400
Stepper
AccountEmail confirmed
WorkspaceNamed and branded
Invite the teamThree seats left
BillingAfter the trial
Inputs
seats
🇺🇸 +1
Rate this release
API key
sk_live_7Kd2Rq9XmB4tPw
Editor
Chat
Today
PL
PriyaThe CSV export drops the last row on files over 10 MB.
PL
Can you check whether the stream is flushed before the response closes?
8:14 AM
Found it — the writer is buffered and never flushed on the last chunk. Patch is up.
8:16 AM
PL
QR code — encoded in the browser, no library and no network call
Video
4:12
Masonry gallery with lazy loading
Jumbotron

Everything you need on the first page load

One stylesheet, one link tag, and no dependencies. Every component on this page came out of the box.




Tooltips

There are two of them and the difference matters. .tooltip is a ::after on the trigger with the text in data-tip — no extra markup, no JavaScript, nothing to keep in sync. But it is pinned above the trigger and cannot flip, so near the top of a scrollport it runs off the edge, and it hides itself on coarse pointers because a hover tip never worked on a phone anyway. .tip is a real popover placed with CSS anchor positioning, so it can flip, and it carries an arrow that stays pointed at its anchor. Reach for .tooltip for a short label on an icon button in the middle of a page; reach for .tip when the text is longer, has to survive an edge, or should open on click.

.tooltip — CSS only, hover or focus, never flips, gone on touch
Rate limits
.tip — anchored popover, flips at an edge, carries an arrow
The integration suite timed out after 10 minutes waiting on a database container that never became healthy. Re-run the job, or raise the health check timeout in the workflow file.
24 seats at $32.50, less the 10% annual discount, plus $62.16 tax. Billed to Northwind Traders.

Optional libraries

Deck's core stays zero-dependency. deck-adapters.js detects a library if you've loaded it and hands that job over, keeping Deck's markup and styling unchanged. Load none and everything still works.

JobDeck aloneWith a libraryVerdict
Placement CSS anchor positioning Floating UI, 9 KB Library only where anchor positioning is missing
Rich text execCommand, deprecated Tiptap or Quill Use the library
Charts CSS charts that retheme Chart.js, themed by Deck CSS for tiles, Chart.js for real axes
Drag and drop Native HTML DnD, poor on touch SortableJS Use the library
Icons 75-icon sprite, trimmable to what you use Lucide, 1500 icons Sprite is enough for Deck; Lucide for the rest
Long lists content-visibility A virtualizer, 5 KB Keep the browser; it doesn't break find-in-page
Dates and locales Intl, built in date-fns or similar Keep Intl
What is running right now

Call Deck.adapters.report() in the console. It names what is actually doing each job on this page, which is what you want when a component behaves differently between two environments.

Anchored tooltips and popovers — these flip and shift on their own
Placement is native. Scroll the page to the edge and it flips instead of running off.
Cascade layers

An ordering mechanism that sits above specificity. A rule in a later layer beats one in an earlier layer no matter how the selectors compare, which is how your CSS overrides Deck without !important.

Kanban with drag reordering
Backlog 3
#418 · CSV export truncates
api-gateway · 3 points
#421 · Dark mode focus rings
design-system · 1 point
#423 · Rate limit headers
api-gateway · 2 points
Drop an issue here
In progress 1
#427 · Search pagination
search-indexer · 5 points
Drop an issue here
Shipped 0
Drop an issue here
Virtualized grid — 5,000 rows, no virtualization library

One line of CSS. Off-screen rows are skipped during layout, style, and paint, but they stay in the DOM — so Ctrl+F still finds them and the print stylesheet still prints them, which a JavaScript virtualizer breaks. No library, no fixed row height, no scroll listener.

Order Customer IDCustomerRepStatus Total
Datepicker localization — Intl, not a date library

Print

Invoices, packing slips, and reports still end up on paper. Print this page and the nav, tab bar, buttons, toasts, and theme dock drop out; the grid unfreezes and prints every column; the mobile card fallbacks revert to real tables; dark mode is forced back to light.

.page-break .keep-together .no-print .print-only .print-keep
This line only exists on paper

The block below is hidden on screen and appears in the print preview.

New issue
Search
Upload a file