keen_web_daterangepicker

Phoenix LiveView wrapper for @keenmate/web-daterangepicker — a themeable date & date-range picker web component with single/range/multiple selection, a time picker, month grids, disabled-date handling, special-date badges, and full keyboard navigation.

One package covers both plain HEEx and LiveView. The upstream JS + CSS are bundled, so no npm install is required.

What's New in v1.0.0-rc.1

First release candidate. Bundles upstream @keenmate/web-daterangepickerv2.0.0-rc03 — no npm install.

Install

def deps do
[
{:keen_web_daterangepicker, "~> 1.0"}
]
end

Currently a release candidate. Only 1.0.0-rc.* is published so far, and Mix skips pre-releases for a plain ~> 1.0 constraint. Until 1.0.0 is final, opt in by requiring the pre-release explicitly:

{:keen_web_daterangepicker, "~> 1.0.0-rc"}

Wire up the assets

The bundled JS and CSS live in this library's priv/static/ directory.

Quick start — the installer

On a standard esbuild Phoenix app, let the installer wire everything for you:

mix keen_web_daterangepicker.install

It edits assets/js/app.js (imports + LiveSocket hook registration) and assets/css/app.css (stylesheet import), idempotently — re-running is safe. Pass --dry-run to preview. Anything it can't confidently patch is left untouched and printed as a manual step. To wire it by hand, use one of the two paths below.

Path A — import from deps/ (esbuild, the Phoenix default)

In assets/js/app.js:

import KeenWebDaterangepickerHook from "../../deps/keen_web_daterangepicker/priv/static/keen_web_daterangepicker_hook.js";
import "../../deps/keen_web_daterangepicker/priv/static/daterangepicker.js";
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { KeenWebDaterangepickerHook },
params: { _csrf_token: csrfToken }
});

In assets/css/app.css:

@import "../../deps/keen_web_daterangepicker/priv/static/daterangepicker.css";

The web component injects its own styles into its Shadow DOM, so the CSS import is only strictly required for positioning_mode="inline" (which renders in light DOM) and for overriding the theme variables at document level. Importing it is harmless and recommended.

Path B — serve directly from the dep's priv/static

In your endpoint, add another Plug.Static:

plug Plug.Static,
at: "/keen_web_daterangepicker",
from: {:keen_web_daterangepicker, "priv/static"},
gzip: false,
only: ~w(daterangepicker.js daterangepicker.css keen_web_daterangepicker_hook.js)

Then reference /keen_web_daterangepicker/daterangepicker.js from your layout <script type="module"> tag and the CSS from a <link>.

Use the component

Import the component in the module where you render templates (a LiveView, a LiveComponent, or your MyAppWeb.html_helpers/0):

import Keenmate.WebDaterangepicker.Components

Declarative — no JavaScript needed

<.web_daterangepicker
id="checkin"
placeholder="Pick a date"
min_date="2026-01-01"
max_date="2026-12-31"
/>

Date range with two months and a summary

<.web_daterangepicker
id="stay"
selection_mode="range"
visible_months_count={2}
disabled_weekdays={[0, 6]}
disabled_dates_handling="block"
/>

LiveView events

Set hook={true} and the hook forwards the upstream date-select, change, and custom-action events to your LiveView (pass a string instead to name a custom hook):

<.web_daterangepicker
id="stay"
hook={true}
selection_mode="range"
/>
def handle_event("web_daterangepicker:change", %{"id" => "stay", "value" => value}, socket) do
# value is a calendar-date string, e.g. "2026-04-30 - 2026-05-04"
{:noreply, assign(socket, :stay, value)}
end

Dates are strings, not timestamps.<web-daterangepicker> is a calendar-date picker. The value the hook forwards is the upstream formattedValue — a "YYYY-MM-DD" string. Keep dates as strings end-to-end; converting to an ISO timestamp (toISOString()) shifts them a day for non-UTC users.

Driving the component from the server

Because the element renders phx-update="ignore", LiveView's DOM patcher won't push new values or restrictions to it. Use Keenmate.WebDaterangepicker.push_update/3 (the sanctioned channel — it sends the event KeenWebDaterangepickerHook listens for):

# Cascading pickers — check-in changed, raise the check-out minimum
def handle_event("web_daterangepicker:change", %{"id" => "checkin", "value" => v}, socket) do
{:noreply, Keenmate.WebDaterangepicker.push_update(socket, "checkout", min_date: v)}
end
# Server-authoritative availability — push freshly-booked dates as disabled
Keenmate.WebDaterangepicker.push_update(socket, "stay", disabled_dates: booked_dates(listing))

Snake_case keys become camelCase JS properties, so any documented property works (value, min_date, max_date, disabled_dates, special_dates, selection_mode, disabled, …). Only the keys you pass are sent. The target needs hook={true} and a matching id.

Server-driven feedback (loader / summary / message)

Upstream v2.0.0 gave the picker's message, summary, and loader blocks an imperative API. The wrapper exposes it as LiveView helpers, so you can drive the picker's feedback UI from the server — a spinner while an async lookup runs, then the result — with no client JavaScript. Like push_update/3, these need hook={true}:

# Show a spinner in the summary block, look the price up, then pin it
def handle_event("web_daterangepicker:change", %{"id" => "stay", "value" => _v}, socket) do
send(self(), :price_it)
{:noreply, Keenmate.WebDaterangepicker.show_loader(socket, "stay", :summary)}
end
def handle_info(:price_it, socket) do
{:noreply,
socket
|> Keenmate.WebDaterangepicker.hide_loader("stay", :summary)
|> Keenmate.WebDaterangepicker.show_summary("stay", "<strong>€474</strong> / 6 nights")
|> Keenmate.WebDaterangepicker.show_message("stay", "Available!", type: "success", auto_hide: 3000)}
end

Helpers: show_message/4 · hide_message/2 · toggle_message/4 · show_summary/3 · hide_summary/2 · refresh_summary/2 · show_loader/3 · hide_loader/3 · toggle_loader/3 (loader target is :calendar | :message | :summary). All are thin wrappers over push_call/4, which invokes any element method by name.

Special dates (holidays, events, prices)

Decorate specific days with badges, tooltips, and CSS classes. special_dates is a property-only input upstream, so the wrapper ships it as JSON and the hook applies it — it requires hook={true}:

<.web_daterangepicker
id="calendar"
hook={true}
special_dates={[
%{date: "2026-12-25", badge_text: "🎄", day_tooltip: "Christmas", day_class: "holiday"},
%{date: "2026-07-04", badge_text: "🎆", day_tooltip: "Independence Day", day_class: "holiday"}
]}
/>

The map keys follow the *_member attributes (date_member, badge_text_member, day_class_member, …); the defaults above match upstream's canonical shape.

Form integration

Pass a Phoenix.HTML.FormField and the component fills in id, name, and the initial value. Because the picker isn't form-associated, the wrapper renders a hidden input and the hook keeps it in sync — so form integration needs hook={true}:

<.simple_form for={@form} phx-change="validate">
<.web_daterangepicker field={@form[:stay]} hook={true} selection_mode="range" />
</.simple_form>

phx-change and phx-submit then see the selected value in params[form_name]["stay"] just like a native input.

Attributes

Every documented attribute from the upstream component is exposed as a typed attr/3. See Keenmate.WebDaterangepicker.Components.web_daterangepicker/1 for the full list, or the upstream usage docs for what each one does.

Snake_case in HEEx maps to kebab-case on the rendered element: selection_modeselection-mode, visible_months_countvisible-months-count, etc.

Theming

The picker is styled entirely through CSS custom properties, in a two-tier cascade: component tokens (--drp-*) each fall back to a shared design token (--base-*), so it works out of the box, inherits a design system when one is present, and is overridable per-instance from HEEx via class / style.

See the Theming guide for the three integration paths:

Versioning

keen_web_daterangepicker versions are independent of @keenmate/web-daterangepicker. The bundled upstream version is reported by:

Keenmate.WebDaterangepicker.upstream_version()
#=> "2.0.0-rc03"

For LLMs and coding agents

A flat-text knowledge base for coding agents ships in the package under the ai/ folder — modelled on the sibling keen_web_multiselect layout but written for this wrapper. Browse it in the repository, or read it from deps/keen_web_daterangepicker/ai/ in a consuming app: start at ai/INDEX.txt (keyword index + common questions) or ai/cookbook.txt (copy-paste recipes).

On hexdocs, see the Using with AI agents page. ex_doc also publishes a machine-readable llms.txt for the package (the llms.txt convention), so agents that fetch hexdocs.pm/keen_web_daterangepicker/llms.txt get a structured index of the docs.

License

MIT.