AddToCalendar
Server-side calendar links and ICS generation for Phoenix LiveView apps.
Generates Google Calendar / Outlook.com URLs and downloadable .ics files
from plain Elixir Date / Time structs — no JavaScript dependency, no
timezone database required.
Installation
Add add_to_calendar to your list of dependencies in mix.exs:
def deps do
[
{:add_to_calendar, "~> 0.1.0"}
]
end
phoenix_live_view is an optional dependency — only required if you use
AddToCalendar.Component.
Usage
Phoenix component
AddToCalendar.Component.calendar_button/1 renders a ready-made dropdown
using a native <details>/<summary> element — no JavaScript required.
<AddToCalendar.Component.calendar_button
name={@event.title}
start_date={@event.starts_on}
start_time={@event.start_time}
end_date={@event.ends_on}
end_time={@event.end_time}
timezone={@event.timezone}
location={@full_address}
description={@event.description}
/>
The :providers attr controls which options appear, in order:
"google"— Google Calendar (opens in new tab)"outlook"— Outlook.com (opens in new tab)"apple"— iCal download labelled "Apple Calendar""ical"— iCal download labelled "iCal / Other"
If using Tailwind CSS, add this package to your content paths so its class names are included in the build:
content: ["../deps/add_to_calendar/lib/**/*.ex"]
Styling
class— applied to the outer<details>wrapper.button_class— applied to the<summary>trigger, replacing the default (rounded px-2.5 py-1 text-sm font-medium ring-1 ring-inset ring-current hover:bg-black/5) rather than merging with it.icon— set tofalseto hide the calendar glyph on the trigger.
<AddToCalendar.Component.calendar_button
...
class="inline-block"
button_class="rounded-full bg-indigo-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-indigo-500"
/>
The dropdown panel and its links aren't exposed as attrs — their classes
(bg-white, border-gray-200, text-gray-700, etc.) are fixed. If you need
to restyle the panel itself, wrap the component, copy component.ex into
your app or create a MR fixing this.
Without Phoenix component
event = %AddToCalendar.Event{
name: "Pickleball League",
start_date: ~D[2026-07-15],
start_time: ~T[09:00:00],
end_date: ~D[2026-07-15],
end_time: ~T[11:00:00],
timezone: "Pacific/Auckland",
location: "123 Main St, Auckland",
description: "Weekly round-robin"
}
AddToCalendar.google_url(event)
# => "https://calendar.google.com/calendar/render?..."
AddToCalendar.outlook_url(event)
# => "https://outlook.live.com/calendar/0/action/compose?..."
AddToCalendar.ics_content(event)
# => "BEGIN:VCALENDAR\r\n..."
AddToCalendar.ics_data_uri(event)
# => "data:text/calendar;charset=utf-8;base64,..."
Development
Toolchain versions are pinned in .tool-versions (compatible with
mise or asdf).
mix deps.get
mix check
A Dockerfile is provided for a reproducible environment matching
.tool-versions:
docker build -t add_to_calendar .
docker run --rm add_to_calendar
Previewing AddToCalendar.Component
This is a standalone library, so there's no host app to render calendar_button/1 in a browser
while iterating on its styling. A dev-only Phoenix Storybook
app under dev/ fills that gap, with stories under dev/storybook/.
mix phx.server
Then visit http://localhost:4000. Elixir changes (e.g. to component.ex) and Tailwind
CSS changes both live-reload the page. This tooling only exists for MIX_ENV=dev (see
elixirc_paths and the application/0 mod in mix.exs) and is excluded from the published
package.