@cahyo-dimas/freeday

中文文档
3.18.0发布于 2026/11/07MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 4387

Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).

原始使用文档
文档目录13 节
  1. Build
  2. Use in a project
  3. First: which entry point is yours?
  4. 1. Include the CSS (required)
  5. 2. Include the JS enhancers (optional, zero dependencies)
  6. 3. Theming: 3 axes via data-* on the root
  7. Framework integration (SPA)
  8. Token rules: 3 tiers (don't break them)
  9. Repo structure
  10. Component inventory
  11. Accessibility
  12. Browser support
  13. License

Freeday

English · Bahasa Indonesia

More free days for devs. The UI kit is ready to use.

Live docs Release

A token-driven, framework-agnostic UI kit. One source of truth for color, typography, spacing, and components. Blueprint: docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md. Living reference: cahyo-dimas.github.io/freeday-ui-kit, or open docs/index.html directly in a browser.

🚀 Starting a project? Step-by-step per stack (HTML · Vue · React · Blazor): docs/getting-started.md.

📘 Which class exists, and what's its markup? The whole public surface in one flat file: COMPONENTS.md. Which one to reach for, and when: USAGE.md. A full screen assembled: docs/reference-screen.html.

🤖 Building with an AI agent (or vibe-coding)? No model knows Freeday from training, so give it docs/agent-onboarding.md (paste-ready project instructions + a migration mapping table).

Build

node tokens/build.mjs   # tokens.json -> dist/freeday.tokens.css; bundles dist/freeday.css;
                        #   dist/freeday.bundle.css (tokens+components); copies dist/*.js + dist/freeday.js
npm test                # build-transform tests + WCAG contrast (node:test)

dist/ is committed, so consumers don't have to build it themselves.

Use in a project

First: which entry point is yours?

Most of Freeday is markup + fdy-* classes and is identical everywhere. Eleven interactive components also ship a typed wrapper. In those stacks, use the wrapper rather than raw markup + enhancer:

Your stackImport the eleven fromBinding
Vue 3@cahyo-dimas/freeday/vuev-model
React 18/19@cahyo-dimas/freeday/reactvalue + onChange
Blazor (net8.0)Freeday.Blazor RCL@bind-Value
Static HTML · Svelte · server-rendered templates(no wrapper) raw markup + enhancers, belowfdy-* events

The thirteen: FdyCombo · FdyDatepicker · FdyMonthpicker · FdyDateRange · FdyTimepicker · FdyAutocomplete · FdyCascade · FdyCfl · FdyChart · FdyTable · FdyModal · FdyDrawer · FdyAppShell. Hand-writing the raw markup for these inside Vue/React/Blazor looks fine at first, because the first render is correct. Then it breaks quietly: DOM the framework renders later is never hydrated, and the widget's state ends up in the DOM instead of your framework's. Everything else (button, card, badge, alert, table markup, layout, and the interactive components with no wrapper) is the same plain markup in every stack. Hydrate the latter with useFreeday (Vue/React) or FreedayBlazor.initAll (Blazor). Full walkthrough per stack: docs/getting-started.md.

As a package (Vue/React/Blazor/Vite, or any project with a bundler):

npm i @cahyo-dimas/freeday
import '@cahyo-dimas/freeday/css';   // tokens + components (single file)
import '@cahyo-dimas/freeday';       // all JS enhancers (auto-init [data-fdy-*])
// granular if needed: '@cahyo-dimas/freeday/tokens' · '@cahyo-dimas/freeday/css/components' · '@cahyo-dimas/freeday/enhancers/<name>' · '@cahyo-dimas/freeday/media' (@custom-media breakpoints)

Set the theme on your app root: <html data-theme="light" data-density="comfortable">. dist/ is committed and published, so install needs no build step; minification is left to the consumer's bundler. Because it's on public npm, npm ci runs in CI without auth or an SSH key.

From source (no registry): npm i github:cahyo-dimas/freeday-ui-kit.

Or link the files directly (no build):

1. Include the CSS (required)

<html lang="en" data-theme="light" data-density="comfortable">
<link rel="stylesheet" href="dist/freeday.tokens.css">  <!-- tokens: color, spacing, etc. -->
<link rel="stylesheet" href="dist/freeday.css">          <!-- fdy-* components -->

Component classes are prefixed fdy- (e.g. fdy-btn, fdy-card, fdy-badge). Use them directly in any framework's markup: Vue, React, Blazor, or plain HTML.

⚠️ Load the fonts yourself. The package doesn't. The type tokens name Sora / IBM Plex Sans / JetBrains Mono but Freeday bundles no font files. Load them (e.g. @import '@fontsource/sora/700.css' …) or override --font-display/--font-body/--font-mono. Otherwise the kit renders in the system fallback and looks unfinished. See docs/getting-started.md §Core concepts.

Which token/role to use when → USAGE.md. Freeday enforces consistent values; USAGE.md is the doctrine that also makes decisions consistent: type roles (.fdy-title-page/-section/-card), spacing rhythm, elevation, one-primary-per-screen, the --tone-1…8 categorical palette, and the page- composition primitives (.fdy-page, .fdy-page-section, .fdy-stats). Every app starts in .fdy-app.

Scope: components + tokens, not layout. Freeday owns components and design tokens; the only layout helpers are .fdy-hidden / .fdy-visually-hidden plus the page-composition primitives above. Bring your own grid layer. Pair it with a utility framework (Tailwind, UnoCSS…) run utilities-only with preflight OFF (Freeday's base.css is the reset). base.css is a light reset (it doesn't strip ul/ol/p margins, so use .fdy-list-reset or a Freeday list component), and the spacing/radius/duration scales are public custom properties (--space-0…--space-24, …) you can build your utility theme on. Full notes: docs/getting-started.md §Core concepts.

.fdy-btn is already the primary button. There's no separate .fdy-btn--primary modifier. Modifiers for other variants: --ghost, --danger, --text, --sm, --lg, --icon (see docs/index.html).

2. Include the JS enhancers (optional, zero dependencies)

Interactive components (dropdown, tabs, table, choose-from-list, datepicker, upload, toast) need JS. Two ways:

<!-- a) one bundle with every enhancer -->
<script src="dist/freeday.js" defer></script>

<!-- b) or pick only the per-file ones you use -->
<script src="dist/freeday-select.js"     defer></script>  <!-- [data-fdy-combo] -->
<script src="dist/freeday-tabs.js"       defer></script>  <!-- [data-fdy-tabs] -->
<script src="dist/freeday-table.js"      defer></script>  <!-- [data-fdy-table] -->
<script src="dist/freeday-cfl.js"        defer></script>  <!-- [data-fdy-cfl] + <dialog> -->
<script src="dist/freeday-datepicker.js" defer></script>  <!-- [data-fdy-datepicker] -->
<script src="dist/freeday-upload.js"     defer></script>  <!-- [data-fdy-dropzone] -->
<script src="dist/freeday-toast.js"      defer></script>  <!-- Freeday.toast({...}) -->

Each one auto-inits [data-fdy-*] on DOMContentLoaded. They're idempotent, and they degrade to plain markup when the script never loads.

EnhancerMarkup hookEvent / API
freeday-select[data-fdy-combo]fdy-change {value} · window.FreedayCombo
freeday-tabs[data-fdy-tabs]window.FreedayTabs
freeday-table[data-fdy-table] (+ [data-fdy-filter], [data-fdy-table-bulk])fdy-table-change · window.FreedayTable
freeday-cfl[data-fdy-cfl] → <dialog>fdy-cfl-select {row}/{rows} · window.FreedayCfl
freeday-datepicker[data-fdy-datepicker], [data-fdy-daterange]fdy-datepicker-change {value,date} · window.FreedayDatepicker
freeday-timepicker[data-fdy-timepicker]fdy-time-select {value} · window.FreedayTimepicker
freeday-datetime[data-fdy-datetimepicker]fdy-datetime-change {date,time,value} · window.FreedayDatetime
freeday-cascade[data-fdy-cascade] (nested <ul> model)fdy-cascade-change {value,path,labels} · window.FreedayCascade
freeday-mask[data-fdy-mask], [data-fdy-password]fdy-mask {value,raw} · window.FreedayMask
freeday-number[data-fdy-number]— (native input/change) · window.FreedayNumber
freeday-form[data-fdy-validate] (form)fdy-form-invalid/-valid · window.FreedayForm
freeday-chip[data-fdy-chips], .fdy-chip__removefdy-chip-change/fdy-chip-remove · window.FreedayChip
freeday-upload[data-fdy-dropzone]fdy-upload-add/-remove · window.FreedayUpload
freeday-toast—Freeday.toast({variant,title,message,timeout})

Wide tables (many columns) need a wrapper for horizontal scroll: wrap .fdy-table in .fdy-table-wrap (a standard table, includes the border + shadow shell) or .fdy-table-scroll (plain scroll, no shell, so it works standalone or inside .fdy-datatable, which already has its own border/shadow shell). Without one of these wrappers, a wide table overflows its container instead of scrolling.

3. Theming: 3 axes via data-* on the root

  • data-theme="light|dark" redefines the semantic tokens (bind it to your app's theme state). Works on any ancestor, not just the root: <section data-theme="dark"> inverts that region and every component inside it follows (see USAGE.md §5b).
  • data-density="comfortable|compact" sets control height (--control-h) for data-dense screens. This auto-applies only to Freeday's built-in controls (button, input, combo, etc.); custom/hand-built components must read --control-h themselves (e.g. height: var(--control-h)) to shrink or grow when data-density changes.
  • (roadmap) data-style for alternative visual variants.
  • The breakpoint scale (sm/md/lg/xl = 600/960/1280/1920px, matching the src/components/breakpoints.css utilities) is available to your app in both languages: import { breakpoints } from '@cahyo-dimas/freeday/breakpoints' for JS (matchMedia, utility variants), and @import '@cahyo-dimas/freeday/media' for CSS, which declares each one as a @custom-media so a rule reads @media (--fdy-below-sm) instead of a retyped literal (needs PostCSS; opt-in). The object also carries the two widths that are not ramp steps and must not be rounded to one: nav (721, the shell's drawer↔sidebar switch) and filterbar (640, where .fdy-filterbar stacks). See COMPONENTS.md for what each governs.

Framework integration (SPA)

Full library map: docs/integrations.md. Each area (forms/validation, charts, tables, dates, overlays, etc.) mapped to the ecosystem library you'd normally reach for (Zod/Yup, Chart.js, TanStack Table, date-fns, Floating UI, …), plus how to bridge it and the Vue/React/Blazor bindings. Read it when starting a new project.

Ready-made adapters for Vue, React and Blazor. All thin: the enhancer stays the source of truth; the adapter only hydrates and bridges events. Each ships a working invoice screen:

// Vue 3: @cahyo-dimas/freeday/vue
import { useFreeday } from '@cahyo-dimas/freeday/vue';
const root = ref<HTMLElement | null>(null);
useFreeday(root);                     // @fdy-cascade-change="…" (typed detail)
// React: @cahyo-dimas/freeday/react
import { useFreeday } from '@cahyo-dimas/freeday/react';
const root = useRef<HTMLDivElement>(null);
useFreeday(root);                     // fdy-* events bubble → listen at the root
@* Blazor: Freeday.Blazor RCL (net8.0), native typed <FdyX> with @bind, 11/11 parity *@
@using Freeday.Blazor
<FdyCombo TValue="string" @bind-Value="_status" Options="_statusOptions" AriaLabelledby="lbl" />
<FdyTable TRow="Invoice" Columns="_cols" Rows="_rows" RowKey="@(i => i.Code)" PageSize="10" />
@* Fallback for raw markup: window.FreedayBlazor.initAll / .on interop *@
FrameworkAdapterWorking example
Vue 3@cahyo-dimas/freeday/vue (useFreeday + v-model components)examples/vue-faktur/ (npm install && npm run dev)
React 19@cahyo-dimas/freeday/react (useFreeday + value/onChange components)examples/react-faktur/ (npm install && npm run dev)
Blazor WASM (net8.0)Freeday.Blazor RCL (<FdyX> + @bind) · /blazor interopexamples/blazor-faktur/ (dotnet run)

Full library map and patterns: docs/integrations.md.

Enhancers auto-init once on load. For DOM rendered dynamically (Vue/React/Blazor):

  • Reuse the enhancer: after a mount or route change, call window.FreedayTable.initAll(el) (or the global initAll()). Safe to repeat, since each init is guarded by an idempotent flag. Bridge to framework state through the events it emits (e.g. listen for fdy-cfl-select, fdy-datepicker-change).
  • Or re-implement: write your own framework component, keeping the same markup + ARIA contract + fdy-* classes (see docs/index.html). The enhancer is the reference implementation, not a requirement. For choose-from-list, make it a controlled component (fetchPage callback + server cache) rather than mirroring into a global store.

Token rules: 3 tiers (don't break them)

Tier 1 PRIMITIVE  raw ramp (--azure-600…), NEVER used in components
Tier 2 SEMANTIC   roles (--color-primary, --color-surface…), change on theme & re-brand
Tier 3 COMPONENT  --fdy-<component>-<prop>, optional local override

Components only touch Tier 2/3. Need a new value → compose → extend a modifier → only then create.

Repo structure

tokens/tokens.json     source of truth (edit here)
tokens/build.mjs       generator (pure Node, zero dependencies)
src/base.css           reset + utilities
src/components/*.css    one file per component (fdy-*)
src/*.js               optional JS enhancers (reference, vanilla)
dist/                  build output (COMMITTED):
  freeday.tokens.css   semantic tokens (light/dark/compact)
  freeday.css          every component (NO tokens)
  freeday.bundle.css   tokens + components, link this one
  freeday.js           bundle of every enhancer (single <script>)
  freeday-*.js         per-file enhancers
COMPONENTS.md          every class + markup skeleton + a11y contract
USAGE.md               the usage doctrine (which token/role when)
docs/index.html        living reference / demo site
docs/reference-screen.html  one complete screen, assembled
docs/agent-onboarding.md    onboarding for AI coding agents
docs/screen-states.md       the screens and states every app needs (404, empty, loading…)
reference/             input material, never shipped (port source + layout archetypes)

Component inventory

  • Foundation: semantic colors, typography (Sora/IBM Plex Sans/JetBrains Mono), 4px spacing scale, radius & elevation, motion, accessibility checklist.
  • Actions & forms: button, input (+error), input-group (currency/%/icon), checkbox/radio/switch, select fdy-combo (APG), autocomplete, cascade select (hierarchical drill-down), choose-from-list (field + single/multi dialog), date / time / datetime picker, file upload (dropzone + per-file state), password reveal + input mask, form validation (Constraint Validation API → accessible errors).
  • Data: table, data table (search · sort · per-column filter text/enum/number · bulk actions · pagination · selection), states (empty/loading/error).
  • Feedback: alert, toast, tooltip. Navigation: tabs, breadcrumb, pagination.
  • Display: card, badge, avatar, chip (default/removable · choice · filter), description list, progress, spinner, skeleton.
  • Layout: app shell, accordion (native <details>), modal (native <dialog>), divider, kbd.

Accessibility

WCAG AA contrast (light & dark), audited automatically by test/contrast.test.mjs: it resolves the token graph, composites semi-transparent -soft fills over their surface, and enforces every pair (text 4.5:1; control borders / icons 3:1, WCAG 1.4.11). Form control borders use --color-control-border (≥3:1); decorative borders are intentionally lighter. :focus-visible is always visible, native HTML comes before ARIA, interactive components follow the WAI-ARIA APG patterns, and prefers-reduced-motion is respected. Status is never conveyed by color alone. See the "Accessibility" section in docs/index.html.

Browser support

Chrome 105 · Safari 15.4 · Firefox 121. The floor is set by :has(), and by the Safari 15.4 generation of native <dialog>/::backdrop and accent-color. conic-gradient and scroll-snap are older still.

test/baseline.test.mjs reads those three numbers out of the line above, enumerates the CSS features both shipped stylesheets use, and fails when one of them needs a release the floor does not reach. Until that test existed the floor was a sentence nobody could enforce, and the day it was written five releases had gone out in one day, four of them fixing Safari defects found on an iPhone 7 after shipping.

color-mix() used to set that floor at Safari 16.4, and it no longer does. It was never worth it: a declaration that mixes a token carries var(), so it is assumed valid at parse time and only fails at computed-value time, where the property drops to its initial value instead of to a fallback line. On an iPhone that stopped at iOS 15 that turned 17 border-color declarations into currentColor, which is a thick black outline on roughly everything. Every alpha in the kit is now written rgba(var(--color-primary-rgb), .26), which is the same colour and parses back to Safari 9; the four rules that cannot be answered statically carry a real @supports counterpart. test/color-mix.test.mjs keeps the next one honest.

The newest things the kit touches reach past that floor on purpose, and each one is listed in test/baseline.test.mjs with what a browser below the floor loses:

  • @container in the choose-from-list dialog is fenced with @supports, and a viewport-width media query stacks the rows underneath it. The case the fallback cannot see is a narrow dialog inside a wide viewport, which is a docked panel on a desktop, and there the dialog keeps the scrolling table it always had.
  • @starting-style and the allow-discrete transitions animate the modal and the drawer. Below the floor the at-rule is skipped and the shorthand is dropped at parse time, so both open and close with no animation, always at their end state and never halfway.
  • text-wrap: balance balances two heading classes. Without it a heading wraps where it used to.
  • overscroll-behavior: contain stops a scroll at the end of the nav and of the sticky page rails. Without it that scroll carries on into the page behind, the way it did before the property existed. There is no CSS substitute below Safari 16; the alternative is a scroll lock in JS, which this shell deliberately does not do.
  • scrollbar-width: none hides the carousel scrollbar, with ::-webkit-scrollbar beside it doing the same job for every WebKit inside the floor.
  • 100dvh caps the sticky rails on screens from 960px. Safari has had the unit since the floor release itself, so the only engines inside the floor without it are the three Chrome releases before it landed there, and on those the cap goes away and a long aside grows past the viewport instead of scrolling inside itself.

Backdrop blur uses -webkit-backdrop-filter + backdrop-filter, and user-select carries its -webkit- twin the same way. The same test asserts both pairings rule by rule, so the standard property never ships alone. There's no CSS build or autoprefixer step, so supporting anything below the floor is the consumer's responsibility.

License

MIT © 2026 Cahyo D. Kurnianto. Free to use, modify, and distribute, as long as the copyright line and license text are included.

版本与发布记录

@cahyo-dimas/freeday 共发布 84 个版本,首次发布于 2026/07/27;过去 12 个月发布了 84 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.18.0latest2026/11/072 天前
3.17.02026/11/054 天前
3.16.12026/11/045 天前
3.16.02026/11/045 天前
3.15.22026/11/045 天前
3.15.12026/11/045 天前

关于 @cahyo-dimas/freeday 的常见问题

@cahyo-dimas/freeday 是什么?

Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).

如何安装 @cahyo-dimas/freeday?

在项目目录运行 npm install @cahyo-dimas/freeday,或使用 pnpm add / yarn add / bun add。

@cahyo-dimas/freeday 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cahyo-dimas/freeday 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@cahyo-dimas/freeday 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cahyo-dimas/freeday 可以商用吗?

@cahyo-dimas/freeday 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cahyo-dimas/freeday 的下载量有多少?

根据 npm 官方下载统计,@cahyo-dimas/freeday 在 2026/10/09 – 2026/11/07 这 30 天内被下载 4,387 次,日均约 146 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cahyo-dimas/freeday 还在维护吗?

最新版本 3.18.0 发布于 2026/11/07(2 天前);至今共发布 84 个版本,其中过去 12 个月发布了 84 个;首次发布于 2026/07/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。