# @devslab/vue-date-rail
[](https://www.npmjs.com/package/@devslab/vue-date-rail)
[](https://github.com/devslab-kr/vue-date-rail/actions/workflows/ci.yml)


[](./LICENSE)
[](https://stackblitz.com/github/devslab-kr/vue-date-rail?file=demo%2FApp.vue&startScript=dev:demo)
**[Live demo](https://devslab-kr.github.io/vue-date-rail/)** · **[Edit on StackBlitz](https://stackblitz.com/github/devslab-kr/vue-date-rail?file=demo%2FApp.vue&startScript=dev:demo)**
Horizontal **infinite-scroll date rail** (day / month strip) picker for Vue 3. Built for mobile-first apps — delivery tracking, booking, scheduling — where a horizontally scrollable strip of days beats a calendar popup.
[한국어 README](./README.ko.md)
## Features
- **Infinite scroll** — days load on demand as you scroll toward either end, with scroll-position compensation (no visual jump when past dates are prepended)
- **`` + ``** — day strip and month strip, sharing one theming system
- **Headless core** — the infinite-scroll logic is exposed as a `useDateRail()` composable; bring your own markup if the default cells don't fit
- **i18n via `Intl`** — pass any BCP 47 locale (`ko`, `en`, `ja`, `de`, `ar`, …); no locale files to import
- **min/max dates & disabled dates** — range clamping stops the infinite scroll at the boundary
- **Marker slot** — render event dots/badges under any date cell
- **Keyboard navigation** — `←` / `→` move a day (month), `Home` jumps to today
- **Desktop-friendly scrolling** — vertical mouse wheel is translated to horizontal scroll, and mouse drag-to-scroll works out of the box (touch uses native scrolling)
- **Themeable** — plain CSS custom properties (`--vdr-*`), no preprocessor, dark-mode friendly
- **Tailwind-ready** — `unstyled` + `cellClass` props and `data-selected` / `data-today` / `data-disabled` attributes for utility-CSS styling
- **Zero dependencies** — only `vue` as a peer dependency
## Install
```bash
npm install @devslab/vue-date-rail
```
## Quick start
```vue
```
Or register globally:
```ts
import { DateRailPlugin } from '@devslab/vue-date-rail';
app.use(DateRailPlugin); // registers and
```
## ``
```vue
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `modelValue` | `Date` | — (required) | Selected date (`v-model`). Emitted normalized to local midnight. |
| `locale` | `string` | `'en'` | BCP 47 locale for `Intl`-based labels. |
| `formatters` | `Partial` | — | Override individual label formatters. |
| `initialPastDays` | `number` | `30` | Days rendered before today on init. |
| `initialFutureDays` | `number` | `30` | Days rendered after today on init. |
| `loadMoreDays` | `number` | `14` | Days appended per infinite-scroll load. |
| `minDate` / `maxDate` | `Date` | — | Clamp the scrollable/selectable range. |
| `disabledDate` | `(date: Date) => boolean` | — | Disable individual dates. |
| `autoScroll` | `boolean` | `true` | Auto-center the selected cell when `modelValue` changes. |
| `showMonth` | `boolean` | `true` | Show the month line in each cell (e.g. "Jul"). |
| `showWeekday` | `boolean` | `true` | Show the weekday line in each cell (e.g. "Tue"). |
| `ariaLabel` | `string` | `'Select date'` | Accessible label for the rail. |
| `unstyled` | `boolean` | `false` | Skip default visual styles (structure only) for utility-CSS styling. |
| `cellClass` | `string \| (ctx) => string` | — | Extra classes per cell; the function receives `{ date, selected, today, disabled }`. |
### Events
| Event | Payload | |
|---|---|---|
| `update:modelValue` | `Date` | `v-model` update (local midnight) |
| `change` | `Date` | Fired alongside `update:modelValue` |
### Slots
| Slot | Props | Description |
|---|---|---|
| `cell` | `{ date, selected, today, disabled }` | Replace the entire cell content. |
| `marker` | `{ date }` | Rendered under the day number — event dots, badges. |
### Exposed methods
| Method | Description |
|---|---|
| `scrollToSelected(behavior?)` | Center the selected cell. |
| `scrollToToday()` | Select today (emits `update:modelValue`) and center it. |
| `reset()` | Regenerate the range around the current `modelValue`. |
## ``
```vue
```
`modelValue` is always emitted normalized to the first of the month. If `modelValue` falls outside the `pastMonths`/`futureMonths` window, the range extends automatically. Slot `item` (`{ month, selected, current }`) replaces the pill content.
## Headless: `useDateRail()`
All the infinite-scroll mechanics without the default markup:
```vue
```
> Cells must carry `data-vdr-key="YYYY-MM-DD"` (use the exported `dateKey`) for `scrollToDate` to find them.
## Theming
Override CSS custom properties on any ancestor (all have built-in fallbacks):
```css
.my-app {
--vdr-accent: #4f82b5; /* selected / today / focus */
--vdr-accent-hover: #416d99;
--vdr-cell-bg: #f4f5f7;
--vdr-cell-hover-bg: #e8eaee;
--vdr-text: #1f2937;
--vdr-text-selected: #ffffff;
--vdr-today-bg: #eef4fa;
--vdr-radius: 12px;
--vdr-gap: 8px;
--vdr-cell-min-width: 56px;
--vdr-fs-day: 1.25rem;
--vdr-month-border: #d6d9de; /* MonthRail pill */
}
```
Dark mode is just another set of overrides — see [demo/App.vue](./demo/App.vue).
### Tailwind CSS (and friends)
Every cell exposes `data-selected` / `data-today` / `data-disabled` attributes, so you can style states with Tailwind's `data-*` variants. Combine with `unstyled` to drop the default look entirely:
```vue
```
Or compute classes per state with a function: `:cell-class="({ selected }) => selected ? '...' : '...'"`. `` offers the same via `unstyled` + `item-class` and `data-selected` / `data-current`. This works the same way with UnoCSS or any attribute-variant utility framework.
## Family
- [vue-month-spinner-picker](https://github.com/jlc488/vue-month-spinner-picker) — iOS-style drum-roll month picker (bottom sheet), the modal sibling of ``
- More open source from [devslab](https://github.com/devslab-kr)
## License
[Apache-2.0](./LICENSE)