SkillAgentSearch skills...

Timescape

A flexible, headless date and time input library for JavaScript. Provides tools for building fully customizable date and time input fields, with support for libraries like React, Preact, Vue, Svelte and Solid.

Install / Use

/learn @dan-lee/Timescape
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

timescape

A powerful, headless library that elegantly fills the void left by HTML's native <input type="time"> and <input type="date">.

timescape is a toolkit for creating custom date and time input components. It helps you handle date and time data easily while giving you full control over the design and presentation. timescape supports multiple libraries, including React, Vue, Preact, Svelte, Solid, and native JavaScript.

Key features such as accessibility and keyboard navigation are at the core of timescape, allowing you to focus on creating user-centric date and time inputs that integrate seamlessly into your projects.

<img src="./assets/timescape.apng" style="max-height:120px" />

See Storybook or check out the examples of how to use it + StackBlitz ⚡ for more demonstrations.

<a href="https://stellate.co" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/dan-lee/timescape/main/assets/badge-dark.svg" /> <img src="https://raw.githubusercontent.com/dan-lee/timescape/main/assets/badge-light.svg" alt="Sponsored by Stellate" /> </picture> </a>

Features

  • 🧢 Headless Architecture: You control the UI – timescape handles the logic.
  • 🧩 Framework Compatibility: Adapters for React, Preact, Vue, Svelte, and Solid.
  • ⚙ Flexible API: Hooks (or equivalents) return getters for seamless component integration. Order of inputs (i.e. format) is completely up to you by just rendering in the order you prefer.
  • 👥 Accessibility: Full A11y compliance, keyboard navigation and manual input.
  • ⏰ Date and time flexibility: Supports min/max dates and 24/12 hour clock formats.
  • 🪶 Lightweight: No external dependencies.
  • 🔀 Enhanced input fields: A supercharged <input type="date/time">, offering additional flexibility.
  • 🤳 Touch device support: Use it on any device, including touch devices.

Installation

# pnpm
pnpm add timescape

# yarn
yarn add timescape

# npm
npm install --save timescape

Examples

<details open> <summary><strong>React</strong></summary>

Edit on StackBlitz ⚡

import { useTimescape } from "timescape/react";

function App() {
  const { getRootProps, getInputProps, options, update } = useTimescape({
    date: new Date(),
    onChangeDate: (nextDate) => {
      console.log("Date changed to", nextDate);
    },
  });

  // To change any option:
  // update((prev) => ({ ...prev, date: new Date() }))

  return (
    <div className="timescape" {...getRootProps()}>
      <input {...getInputProps("days")} />
      <span>/</span>
      <input {...getInputProps("months")} />
      <span>/</span>
      <input {...getInputProps("years")} />
      <span> </span>
      <input {...getInputProps("hours")} />
      <span>:</span>
      <input {...getInputProps("minutes")} />
      <span>:</span>
      <input {...getInputProps("seconds")} />
    </div>
  );
}
</details> <details> <summary><strong>Preact</strong></summary>

Edit on StackBlitz ⚡

This package uses Preact signals, if you want to use it without just use the React implementation in compat mode.

import { effect } from "@preact/signals";
import { useTimescape } from "timescape/preact";

function App() {
  const { getRootProps, getInputProps, options } = useTimescape({
    date: new Date(),
  });

  effect(() => {
    console.log("Date changed to", options.value.date);
  });

  // To change any option:
  // options.value = { ...options.value, date: new Date() }

  return (
    <div className="timescape" {...getRootProps()}>
      <input {...getInputProps("years")} />
      <span>/</span>
      <input {...getInputProps("months")} />
      <span>/</span>
      <input {...getInputProps("days")} />
    </div>
  );
}
</details> <details> <summary><strong>Vue</strong></summary>

Edit on StackBlitz ⚡

<template>
  <div class="timescape" :ref="registerRoot()">
    <input :ref="registerElement('years')" />
    <span>/</span>
    <input :ref="registerElement('months')" />
    <span>/</span>
    <input :ref="registerElement('days')" />
  </div>

  <!-- Change any option -->
  <button @click="options.date = new Date()">Change date</button>
</template>

<script lang="ts" setup>
import { type UseTimescapeOptions, useTimescape } from "timescape/vue";
import { watchEffect } from "vue";

const { registerElement, registerRoot, options } = useTimescape({
  date: new Date(),
});

watchEffect(() => {
  console.log("Date changed to", options.value.date);
});
</script>
</details> <details> <summary><strong>Svelte</strong></summary>

Edit on StackBlitz ⚡

<script lang="ts">
import { derived } from "svelte/store";
import { createTimescape } from "timescape/svelte";

const { inputProps, rootProps, options } = createTimescape({
  date: new Date(),
});

const date = derived(options, ($o) => $o.date);

date.subscribe((nextDate) => {
  console.log("Date changed to", nextDate);
});

// To change any option:
// options.update((prev) => ({ ...prev, date: new Date() }))
</script>

<div class="timescape" use:rootProps>
  <input use:inputProps={'days'} />
  <span>/</span>
  <input use:inputProps={'months'} />
  <span>/</span>
  <input use:inputProps={'years'} />
</div>

</details> <details> <summary><strong>Solid</strong></summary>

Edit on StackBlitz ⚡

import { createEffect } from "solid-js";
import { useTimescape } from "timescape/solid";

function App() {
  const { getInputProps, getRootProps, options, update } = useTimescape({
    date: new Date(),
  });

  createEffect(() => {
    console.log("Date changed to", options.date);
  });

  // To change any option:
  // update('date', new Date())
  // or update({ date: new Date() })

  return (
    <div class="timescape" {...getRootProps()}>
      <input {...getInputProps("years")} />
      <span>/</span>
      <input {...getInputProps("months")} />
      <span>/</span>
      <input {...getInputProps("days")} />
    </div>
  );
}
</details> <details> <summary><strong>Vanilla JS</strong></summary>
import { TimescapeManager } from "timescape";

const container = document.createElement("div");
document.body.appendChild(container);

container.innerHTML = ` 
  <div class="timescape" id="timescape-root">
    <input data-type="days" placeholder="dd" />
    <span>/</span>
    <input data-type="months" placeholder="mm" />
    <span>/</span>
    <input data-type="years" placeholder="yyyy" />
  </div>
`;

const timeManager = new TimescapeManager();

timeManager.date = new Date();

timeManager.subscribe((nextDate) => {
  console.log("Date changed to", nextDate);
});

timeManager.registerRoot(document.getElementById("timescape-root")!);

timeManager.registerElement(
  container.querySelector('[data-type="days"]')!,
  "days",
);
timeManager.registerElement(
  container.querySelector('[data-type="months"]')!,
  "months",
);
timeManager.registerElement(
  container.querySelector('[data-type="years"]')!,
  "years",
);
</details>

Options

The options passed to timescape are the initial values. timescape returns the options either as store/signal or with an updater function (depending on the library you are using).

type Options = {
  date?: Date;
  minDate?: Date | $NOW; // see more about $NOW below
  maxDate?: Date | $NOW;
  hour12?: boolean;
  wrapAround?: boolean;
  digits?: "numeric" | "2-digit";
  snapToStep?: boolean;
  wheelControl?: boolean;
  disallowPartial?: boolean
};

| Option | Default | Description | | ----------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | date | undefined | The initial date. If not set, it will render the placeholders in their respective input fields (if set). | | minDate | undefined | The minimum date that the user can select. $NOW is a special value that represents the current date and time. See more below | | maxDate | undefined | The maximum date that the user can select. $NOW is a special value that represents the current date and time. See more below

View on GitHub
GitHub Stars228
CategoryCustomer
Updated1mo ago
Forks11

Languages

TypeScript

Security Score

100/100

Audited on Feb 18, 2026

No findings