Calix

Theming and customization

Use optional themes or style Calix with your own system.

Calix has no styling dependency. The built-in components expose stable class names and state attributes, so plain CSS, CSS Modules, Tailwind, CSS-in-JS, and design-system wrappers can all target the same markup.

Optional themes

import "@alydev/themes/default.css"; // full default appearance
// import "@alydev/themes/minimal.css"; // structural styling only

Built-in components use the dark theme by default. Pass theme="light" to a calendar, date picker, or time field for the light variant. The theme also responds to prefers-contrast: more.

<DatePicker adapter={gregorian} theme="light" />
<TimeField theme="light" />

CSS variables

The provided themes are CSS-variable based. Override a variable in your app to match your design system:

:root {
  --calix-accent: #6d28d9;
  --calix-on-accent: #ffffff;
  --calix-surface: #17171d;
  --calix-text: #f4f4f5;
  --calix-radius: 10px;
  --calix-cell-size: 2.5rem;
  --calix-font: "Vazirmatn", system-ui, sans-serif;
}

Common tokens include --calix-accent, --calix-on-accent, --calix-surface, --calix-surface-raised, --calix-text, --calix-muted, --calix-border, --calix-hover, --calix-radius, --calix-radius-sm, --calix-cell-size, --calix-gap, --calix-font, and --calix-transition. The default accent remains #8b7cff.

For component states and layout, use --calix-focus-ring, --calix-disabled-opacity, --calix-weekend, --calix-outside-month, --calix-selected-shadow, --calix-calendar-padding, and --calix-popover-z-index.

--calix-font is inherited by Calendar, a DatePicker popover (including its time-selection step), and standalone TimeField. Load the font in your app as usual, then set this token.

Common recipes

Set tokens globally in :root to theme every picker. To theme one picker, attach the same class to its rendered root: use classNames.root for Calendar, classNames.popover for DatePicker, and className for TimeField. A DatePicker popover is portalled, so styling the input wrapper alone will not affect it.

<Calendar adapter={gregorian} classNames={{ root: "brand-picker" }} />
 
<DatePicker
  adapter={gregorian}
  withTime
  classNames={{ popover: "brand-picker" }}
/>
 
<TimeField className="brand-picker" />
.brand-picker {
  --calix-accent: #2563eb;
  --calix-on-accent: white;
  --calix-surface: white;
  --calix-surface-raised: white;
  --calix-text: #172554;
  --calix-muted: #64748b;
  --calix-border: #bfdbfe;
  --calix-hover: #eff6ff;
  --calix-radius: 8px;
  --calix-radius-sm: 6px;
  --calix-cell-size: 2.75rem;
  --calix-gap: 4px;
  --calix-calendar-padding: 1.25rem;
  --calix-font: "Vazirmatn", system-ui, sans-serif;
  --calix-transition: 120ms ease-out;
}
 
.brand-picker .calix-day[data-today] {
  outline-color: #f59e0b;
}
 
.brand-picker .calix-day[data-weekend]:not([data-selected]) {
  color: #dc2626;
}

Keep focus indicators visible when overriding --calix-focus-ring, and let the built-in prefers-reduced-motion rule disable non-essential motion.

State contract

Day buttons carry the following attributes when their state is active:

AttributeMeaning
data-selectedThe day is selected.
data-todayThe day is today.
data-disabledThe day cannot be selected.
data-outside-monthThe day belongs to an adjacent month.
data-range-start / data-range-endA range boundary.
data-in-rangeA day inside a selected or previewed range.
data-focusedThe roving-focus day.
data-weekendSaturday or Sunday.
data-holidayA named holiday is visible on the day.
data-holiday-nameThe visible holiday's name.

The default theme marks holidays and shows their name on hover. Target these attributes to replace that presentation; see Holidays for the data contract.

.brand-picker .calix-day[data-disabled] {
  opacity: 0.35;
  text-decoration: none;
}
 
.brand-picker .calix-day[data-in-range] {
  background: #dbeafe;
}
 
.brand-picker .calix-day[data-range-start],
.brand-picker .calix-day[data-range-end] {
  border-radius: 6px;
}

Slot class names

Pass classNames to Calendar or DatePicker to add classes to every built-in calendar slot: root, header, heading, navButton, months, grid, weekdays, weekday, week, day, footer, footerButton, presets, preset, picker, pickerBody, month, and year. DatePicker also exposes popover, field, input, toggle, clear, dateTime, timeStep, timeBack, timeTitle, and timeActions.

<Calendar
  adapter={gregorian}
  classNames={{
    root: "calendar-shell",
    heading: "calendar-heading",
    navButton: "calendar-nav-button",
    day: "calendar-day",
    footerButton: "calendar-footer-button",
  }}
/>

Use DatePicker's extra slots to target the field and time flow without replacing their markup:

<DatePicker
  adapter={gregorian}
  withTime
  classNames={{
    field: "booking-field",
    input: "booking-input",
    toggle: "booking-toggle",
    popover: "booking-popover",
    timeStep: "booking-time-step",
    timeActions: "booking-time-actions",
  }}
/>

Custom content and layout

Use renderDay to replace a day's content without reimplementing selection or accessibility. header and footer accept either a node or a callback which receives the complete useCalendar API, so custom titles and buttons retain the built-in navigation and selection behavior.

<Calendar
  adapter={gregorian}
  renderDay={(date, label) => <span title={`${date.year}/${date.month}/${date.day}`}>{label}</span>}
  header={(calendar) => <button onClick={calendar.goToToday}>This month</button>}
  footer={(calendar) => <button onClick={calendar.clear}>Reset</button>}
/>

For markup beyond the calendar, compose DatePicker.Root, .Trigger, .Input, and .Content, or render from useCalendar. See Hooks.

On this page