Advanced Options
For power users who want full control over the calendar widget beyond what the dashboard and the embed parameters offer.
CSS custom property overrides
You can override any visual property in the view editor. Open the view, find Theme, expand Advanced CSS Overrides, and enter a JSON object. Overrides are saved with the view and apply to every embed of it. Choose the palette with the palette buttons above the box; the box holds only the overrides.
Each key in the object is converted to a CSS custom property.
The conversion follows a camelCase to --already-kebab-case pattern:
| Theme key | CSS custom property | Controls |
|---|---|---|
primary |
--already-primary |
Accent: active tab, buttons, links, date badges |
primaryText |
--already-primary-text |
Text drawn on top of primary |
background |
--already-background |
The calendar's own ground |
surface |
--already-surface |
Cards, panels, and the layer above the ground |
text |
--already-text |
Primary text |
textSecondary |
--already-text-secondary |
Dates, locations, supporting text |
border |
--already-border |
Base the two below inherit from |
borderControl |
--already-border-control |
Tab strip, nav bars, buttons, dropdowns |
borderGrid |
--already-border-grid |
Weekday header, week columns, event rows |
highlight |
--already-highlight |
Today, hover, and selected states |
error |
--already-error |
Error card accent |
fontFamily |
--already-font-family |
The whole widget's type stack |
fontSizeSm |
--already-font-size-sm |
Supporting text |
fontSizeBase |
--already-font-size-base |
Body text |
fontSizeLg |
--already-font-size-lg |
Event titles |
fontWeightNormal |
--already-font-weight-normal |
Body weight |
fontWeightBold |
--already-font-weight-bold |
Title weight |
radius |
--already-radius |
Corner radius on cards and buttons |
radiusSm |
--already-radius-sm |
Corner radius on small elements |
shadow |
--already-shadow |
Card elevation |
shadowHover |
--already-shadow-hover |
Card elevation on hover |
spacing |
--already-spacing |
Base spacing unit |
Values accept the usual CSS forms for each property: transparent, rgba(...),
none, 0, hex, and hsl(...).
Any color, font or shadow override turns off matching your site. Layout and size keys do not.
calc(), clamp() and var() are not accepted. Theme values are also
rendered into a stylesheet server-side, where an arbitrary value would be an
injection risk, so each one is validated against the grammar for its type. A
value that fails validation is dropped and the palette default applies.
Making the calendar match your site
The default palettes are starting points. Three overrides do most of the work of making the widget stop looking like an embed:
backgroundis the single most important one. If it differs from your page even slightly, the calendar reads as a panel sitting on your site rather than part of it. Set it to your page's exact background color.surfaceis the card layer. Set it equal tobackgroundif your site is flat and has no card treatment.shadow: "none"andradius: "0"remove the floating-card look entirely.
A flat, high-contrast site
For a site with a hard-edged, poster-like design: a cream page, near-black text, a strong accent, and no elevation anywhere.
{
"background": "#f6eadd",
"surface": "#f6eadd",
"text": "#1a1a1a",
"textSecondary": "#5c5147",
"primary": "#a54734",
"border": "#1a1a1a",
"radius": "0",
"shadow": "none"
}Setting surface equal to background removes the card layer, so events sit
directly on the page instead of on tiles.
A photographic background
With the script tag embed and no colors chosen, the calendar is already transparent and takes its text color from your page; see Matching your site. The theme below is for iframe embeds, or when you want to set the colors yourself.
If your calendar sits on top of an image, set background to transparent so
the photograph shows through, and give surface enough opacity to keep text
readable over whatever is behind it.
{
"background": "transparent",
"surface": "rgba(255, 255, 255, 0.92)",
"text": "#1a1a1a",
"primary": "#de0086"
}Check the contrast against the busiest part of your image, not the calmest.
Fonts
With Match site and the script tag, the calendar loads your page's font when it
comes from Google Fonts or Bunny Fonts. Setting fontFamily turns Match site
off, so the rest of this section applies once you set it. Otherwise the
calendar renders in an iframe and does not inherit fonts your page loads.
Naming your brand face in fontFamily alone gets the visitor's fallback. It
will look correct to you if that font is installed on your machine, which is
what makes this easy to miss.
Use a system stack if an exact match is not essential. Nothing loads, text renders immediately, and no request leaves the page:
{ "fontFamily": "system-ui, -apple-system, sans-serif" }
{ "fontFamily": "Georgia, 'Times New Roman', serif" }Or load your brand font with fontUrl. Name the family first in
fontFamily and point fontUrl at the file:
{
"fontFamily": "Figtree, system-ui, sans-serif",
"fontUrl": "https://fonts.gstatic.com/s/figtree/v5/_Xm.woff2"
}The first family names the loaded face. Everything after it is the fallback, used while the font downloads and if it fails.
fontUrl must be https, must end in .woff2, .woff, .ttf or .otf, and
must be served by one of:
| Provider | Host |
|---|---|
| Google Fonts | fonts.gstatic.com |
| Bunny Fonts | fonts.bunny.net |
Anything else is rejected and the fontFamily stack is used instead, so a bad
URL falls back to a system font rather than to invisible text.
The list is short on purpose. Loading a font means every visitor's browser requests it from that host, which reveals their IP to whoever you named, so the embed permits only providers already handling that traffic at scale. If you self-host elsewhere and need it supported, ask and we will add it.
Hierarchy in borders
border sets every rule at once. If your site has a defined hierarchy, split
it: borderControl covers the chrome (tab strip, nav, buttons) and
borderGrid covers the calendar's internal lines. Both default to border,
so setting only border behaves as it always has.
{
"border": "#1a1a1a",
"borderGrid": "rgba(26, 26, 26, 0.12)"
}A strong frame with quiet internal lines, rather than every line equally heavy.
Unknown keys are ignored silently
A key that is not one of the above becomes a CSS custom property nothing reads,
so a typo like backgroundColor has no effect and reports no error. If an
override seems not to apply, check the spelling against the table first.
Sticky header options
The sticky header can be configured granularly as an object instead of a simple boolean:
{
"header": true,
"viewSelector": true,
"tagFilter": false
}Each key controls whether that UI element sticks to the top of the viewport as the visitor scrolls. Set the sticky URL parameter, or the data-sticky attribute on the script tag, to this object to pin specific elements while letting others scroll.
Auto-resize for iframe embeds
The calendar posts { type: 'already-events:resize', height }, with the height in pixels, to its parent window whenever its content height changes. The script tag listens for this message and resizes the iframe on its own. With a plain iframe, add a listener yourself. It must check that the message came from your iframe and from https://embed.already.events, and it must reject heights that are not finite positive numbers. The script tag also rejects heights above 50000 pixels.
const frame = document.querySelector('iframe[src^="https://embed.already.events/"]');
window.addEventListener('message', (event) => {
if (event.source !== frame.contentWindow) return;
if (event.origin !== 'https://embed.already.events') return;
const data = event.data;
if (!data || data.type !== 'already-events:resize') return;
if (typeof data.height !== 'number' || !isFinite(data.height)) return;
if (data.height <= 0 || data.height > 50000) return;
frame.style.height = data.height + 'px';
});If your iframe loads from your own subdomain, compare event.origin with that subdomain's origin instead.
Extending known platforms
These options apply when you run the open-source already-cal widget yourself. A hosted embed does not expose them.
The calendar recognizes URLs from 18+ platforms by default. You can extend the platform list using Already.DEFAULTS:
Already.init({
el: '#calendar',
google: { apiKey: '...', calendarId: '...' },
knownPlatforms: [
...Already.DEFAULTS.knownPlatforms,
{ pattern: /your-site\.com/i, label: 'Visit Our Site' },
],
});Already.DEFAULTS contains all the default configuration values. Spreading it into your config lets you extend rather than replace the built-in platform list.
Callbacks
These options apply when you run the open-source already-cal widget yourself. A hosted embed does not expose them.
You can register callback functions for event-driven behavior:
onEventClick(event, viewName)— called when a visitor clicks an event. Returnfalseto prevent navigation to the detail view. It is called for a plain click or Enter on the event's link. A middle click, or a click with a modifier key held, opens the event in a new tab and does not call it.onViewChange(newView, oldView)— called when the active view changes.onDataLoad(data)— called after events load successfully.onError(error)— called if event loading fails.
Data hooks
These options apply when you run the open-source already-cal widget yourself. A hosted embed does not expose them.
Transform or filter events before they're displayed:
eventTransform(event)— modify each event after enrichment (add fields, change values). Return the modified event.eventFilter(event)— returntrueto keep the event,falseto exclude it.
These hooks run in the pipeline after built-in enrichment (directive extraction, image detection, etc.) and before rendering.