Skip to main content

Tooltips

This guide covers the two ways to show event details without opening the editor: a hover tooltip through the tooltip prop, and a clickable card through eventPopup. Both take a React component, and the calendar owns when it mounts and where it goes.

hover tooltip floating above an event with the event title and time range

How tooltips work​

The tooltip prop takes a React component, not a DOM string. The calendar render layer owns its lifecycle:

  • It listens for hover only when a tooltip component is configured.
  • It resolves the event under the cursor and instantiates the component with the right props.
  • It dismisses the tooltip on mouse leave or when an event card opens.

Two render paths exist:

SurfaceTriggerPositionProps
boxes, bars, grid, listHover an event elementFloats with the cursor{ event }
yearHover a marked day cellAnchored to the cell{ events: CalendarEvent[] }

Year view passes a list because a single day can hold multiple events. Every other view passes one event at a time. A tooltip designed for both should accept both shapes.

The widget never injects a close callback. Tooltips are display-only, with pointer-events: none in the regular overlay path. If you need clicks or buttons inside the popup, use eventPopup instead.

Passing a component​

Import a component and hand it to the tooltip prop:

// App.jsx
import { Calendar } from "@svar-ui/react-calendar";
import EventTooltip from "./EventTooltip.jsx";
import { getData } from "./data";

const { data, date } = getData();

function App() {
return (
<Calendar events={data} view="month" date={date} tooltip={EventTooltip} />
);
}

export default App;

Inside the tooltip component, declare both possible payloads so the same file works in every view:

// EventTooltip.jsx
function EventTooltip({ event, events }) {
if (event) {
return (
<>
<div className="title">{event.text}</div>
<div className="time">
{event.start.toLocaleTimeString()} - {event.end.toLocaleTimeString()}
</div>
</>
);
}

if (events) {
return (
<div>
<div>{events.length} events</div>
{events.map((ev) => (
<div key={ev.id}>{ev.text}</div>
))}
</div>
);
}

return null;
}

export default EventTooltip;

If you only use month/week/day, the event branch is enough. Add the events branch when you also want year view to use the same component.

Positioning and styling​

The render layer handles positioning - your component only needs to render the inner content.

  • In boxes, bars, grid, and list sections, the tooltip is wrapped in a fixed-position layer that follows the pointer.
  • In year sections, it mounts inside a Popup anchored to the day cell.

Style the inside of the component - width, padding, background, typography - and let the calendar place it. The wrapping element sets pointer-events: none, so hover styles inside the tooltip will not trigger.

.event-tooltip {
background: #1e293b;
color: #f1f5f9;
border-radius: 6px;
padding: 8px 12px;
font-size: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
min-width: 140px;
max-width: 260px;
}

When no custom tooltip is provided, year view falls back to a built-in list of events for that day. Setting a custom component replaces the built-in entirely - there is no partial override.

Clickable cards with eventPopup​

A tooltip disappears on mouse leave and cannot hold buttons. When the preview needs to stay open and react to clicks, use the eventPopup prop instead: the calendar mounts your component inside an anchored Popup whenever the user clicks an event.

custom event card popup anchored to a clicked event with title, time range, and a close button

A few rules drive how it behaves:

  • Click detection is movement-aware. The internal click handler listens for mousedown / mouseup on the section container and treats the interaction as a click only when the pointer stays within a 3 px threshold. This is what separates a click from the start of a drag.
  • Resolution is store-based. Event elements expose a data-id. The handler resolves it through api.getEvent(...) to find the stored event, then opens the popup with that event.
  • eventPopup overrides the default click path. Without eventPopup, an event click dispatches select-event (which opens the editor when one is mounted). With eventPopup, the click opens the card instead - select-event is not dispatched.
  • Empty-space clicks dismiss. Clicking outside an event closes the current card. Clicking another event swaps the card to that event.
  • The component renders inside the calendar subtree. It can read any React context exposed by the calendar's parent components - useful for passing app-level data such as resource lists.

The card receives two props: the resolved event object and a close callback. You decide what the card looks like and when it dismisses itself.

// App.jsx
import { Calendar } from "@svar-ui/react-calendar";
import EventCard from "./EventCard.jsx";
import { getData } from "./data";

const { data, date } = getData();

function App() {
return (
<Calendar
events={data}
date={date}
view="week"
eventPopup={EventCard}
views={["day", "week", "month"]}
/>
);
}

export default App;

The card component itself reads its props:

// EventCard.jsx
function EventCard({ event, close }) {
return (
<div className="event-card">
<header>{event.text}</header>
<p>{event.start.toLocaleString()} - {event.end.toLocaleString()}</p>
<button onClick={close}>Close</button>
</div>
);
}

export default EventCard;

close is the supported way to dismiss the popup from inside the card - use it after the user confirms an action, navigates away, or clicks an explicit close control.

Reading app data through context​

Because the card renders inside the calendar's subtree, it can read React context from any ancestor - including contexts you set in the parent that mounts <Calendar>:

// parent component
import { createContext } from "react";
import { Calendar } from "@svar-ui/react-calendar";

export const ResourcesContext = createContext(null);

const resources = [
{ id: "alice", label: "Alice" },
{ id: "bob", label: "Bob" },
];

function Parent() {
return (
<ResourcesContext.Provider value={resources}>
<Calendar events={data} date={date} eventPopup={EventCard} />
</ResourcesContext.Provider>
);
}

The card pulls the same context with useContext:

import { useContext } from "react";
import { ResourcesContext } from "./Parent.jsx";

function EventCard({ event, close }) {
const resources = useContext(ResourcesContext);
const assignee = resources?.find(r => r.id === event.unit_id)?.label;
// ...
}

This pattern keeps domain data out of every event and lets the card resolve it on demand.

Coexisting with the editor​

The default click path opens the editor (when <Editor> is mounted) by dispatching select-event. The eventPopup prop replaces that path, so the editor will not auto-open on click while the card is active.

If you want the card and the editor side by side, route the editor explicitly from a button inside the card. Call api.exec("select-event", { id }) to open the editor for the same event the card is showing:

// EventCard.jsx
import { useContext } from "react";
import { context } from "@svar-ui/react-calendar";

function EventCard({ event, close }) {
const api = useContext(context.api);

const openEditor = () => {
api.exec("select-event", { id: event.id });
close();
};

return (
<>
<button onClick={openEditor}>Edit</button>
<button onClick={close}>Close</button>
</>
);
}

export default EventCard;

context.api is the React context the calendar exposes for child components; see the API reference for the full surface (getState, getReactiveState, exec, fmt, getEvent).

If you prefer a card-only flow, leave the editor out of the tree - eventPopup does not require it.

When to use what​

  • Tooltip - a read-only preview that follows the pointer. tooltip set. No clicks inside, no editor involvement.
  • Editor only - users edit events through the form. No eventPopup. Click selects, editor opens.
  • Card only - users see a read-only or action-driven preview on click. eventPopup set, no <Editor>.
  • Card plus editor - card is the entry point, editor opens from a card action. eventPopup set, <Editor api={api} /> mounted, card calls api.exec("select-event", ...).