Skip to content
KONIGI Patterns

Dashboards / Dashboard features / Share and embed

‹ 10 of 10

Share and embed

People need to share a dashboard while preserving the context needed to understand it.

Updated September 10, 2026

Problem

People need to send a finding outside the dashboard while preserving the context that makes it understandable.

Solution

Offer the sharing format that fits the need. A live link opens the current data with the selected settings. A snapshot preserves a historical result. An embed places a live view elsewhere, while a scheduled export sends a recurring report.

Carry the time range, filters, and visible selections into the shared result. Show whether it is live or frozen and when the data was captured.

Make the audience and access requirements clear before sharing. Let people manage or revoke shared access where supported, and explain what information a public result exposes.

Use when

Others need access to a view, a finding must be preserved, or a report needs regular delivery.

Don’t use when

The recipient has access and the link would do. A snapshot where a link would work creates a second copy that immediately starts diverging from reality.

Trade-offs

Shared results can outlive their relevance or reveal information beyond the intended audience. Embeds can break when source panels change. Scheduled reports and snapshots need review so outdated material does not keep circulating.

Checklist

  • Does a link carry range, filters, variables and selection?
  • Is a snapshot’s capture time stamped on the snapshot itself?
  • Does a shared artefact say what was filtered when it was made?
  • Who can create a public link, and is the consequence visible at the moment of clicking?
  • Do public snapshots expire, and is there any inventory of them?
  • What does an embed show when the source panel is deleted?
  • Do scheduled exports have an owner and a review, or do they run forever?
  • Does a shared artefact carry a route back to the live view?
  • Does a snapshot include data the recipient should not see?
  • Is a stale artefact distinguishable from a current one at a glance?

Compare

Grafana offers the fullest set—direct link with current state, snapshots that can be public, panel embeds and image rendering—which covers every case and puts the public-exposure decision one click away without much ceremony. Datadog leans on scheduled reports and embeddable graphs with per-embed tokens, so an embed can be revoked without deleting the panel it came from. Honeycomb shares the query rather than a rendered result, which is the most robust artefact of the lot: a query re-run next week is honest about being a different answer, where an image pretends it is the same one. Public status pages are the extreme case, built entirely for readers with no access, and they show what a share-first design looks like when it is the product rather than a feature.

Saved view keeps settings for later use. Freshness indicator shows data age. Time-range picker supplies essential context.

Comment and annotate keeps discussion with the data. Data source badge identifies where the shared values came from.

Share and embed anatomy Four mechanisms behind one button—a live link, a frozen snapshot, an embed and a scheduled export—each with the failure it runs into. Below, the same panel shared with its range and filters stamped into it and shared without them. Four needs wearing one button Link Live state, cheapest, best when they have access. Hinges on the URL carrying all of it. Snapshot Frozen data, viewable without access. Solves the writeup that links to a healthy system. Embed Rendered inside a wiki or a portal, updating live, read by people who never open the tool. Export A PDF arriving on a timetable, for people who will never open the tool at all. Checkout errors Shared as an image Checkout errors 14:00-15:00 · eu-west 1 of 6 series isolated Stamped 1 2 1 CONTEXT DOESN'T TRAVEL It loses the range, the filters, and the fact that someone had isolated one series. It looks authoritative and is missing what made it true. 2 STAMP IT IN Any share worth building writes the window and the filters into what it produces. A public snapshot is a data-exposure decision made by whoever clicked the button, usually with no review step, by someone who thinks they are sending a picture.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

The dashboard leaves the tool and has to keep meaning something. A shared link carrying a relative range means a different thing tomorrow, so the dialog has to make the absolute choice visible rather than defaulting quietly.

shadcn
npx shadcn@latest add tabs input button switch label
npm
lucide-react
Tokens
--background--card--card-foreground--muted-foreground--border--chart-1

Checkout errors

14:00-15:00 · eu-west

1 of 6 series isolated

SharePanel.tsxLink, snapshot, embed and export behind one set of tabs, with the range and filters stamped on the preview before anything leaves. Absolute time is the default; public is a switch that says what it does.

import { useState, type ReactNode } from "react";
import { Copy } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

/** Four needs wearing one button, each described by what it is good for. */
export type Mechanism = "link" | "snapshot" | "embed" | "export";
const MECHANISMS: { id: Mechanism; label: string; blurb: string }[] = [
  { id: "link", label: "Link", blurb: "Live state, cheapest, best when they have access. Hinges on the URL carrying all of it." },
  { id: "snapshot", label: "Snapshot", blurb: "Frozen data, viewable without access. Solves the writeup that links to a healthy system." },
  { id: "embed", label: "Embed", blurb: "Rendered inside a wiki or a portal, updating live, read by people who never open the tool." },
  { id: "export", label: "Export", blurb: "A PDF arriving on a timetable, for people who will never open the tool at all." },
];

export type ShareState = {
  title: string;
  /** The window as it stands right now, absolute. */
  range: { from: Date; to: Date };
  /** The relative form the page is actually on, if any: "last 1h". */
  relative?: string;
  filters: string[];
  /** Set when the viewer has isolated part of the chart. It travels or it lies. */
  isolated?: { shown: number; of: number };
};

const hhmm = (d: Date) => d.toISOString().slice(11, 16);
/** What gets written onto everything that leaves. */
export const stamp = (s: ShareState) => [`${hhmm(s.range.from)}-${hhmm(s.range.to)}`, ...s.filters].join(" · ");

/**
 * One panel behind the share button, with the state it will carry shown
 * before anything is produced. The absolute range is the default and the
 * relative one is a switch, so a link that means something else tomorrow is
 * a choice rather than an accident. Public is a switch with its consequence
 * written beside it.
 */
export function SharePanel({ state, url, preview, onCopy, onSnapshot, onExport }: {
  state: ShareState;
  /** The live URL. It must carry the range, filters and selection already. */
  url: string;
  /** The panel as it will leave, rendered small. */
  preview?: ReactNode;
  onCopy: (text: string) => void;
  onSnapshot: (opts: { isPublic: boolean }) => void;
  onExport: (opts: { every: "day" | "week" }) => void;
}) {
  const [absolute, setAbsolute] = useState(true);
  const [isPublic, setPublic] = useState(false);
  const href = absolute || !state.relative ? url : url.replace(/from=[^&]*&to=[^&]*/, `range=${encodeURIComponent(state.relative)}`);
  const embed = `<iframe src="${href}&embed=1" width="600" height="300" title="${state.title}"></iframe>`;

  const copyRow = (text: string, label: string) => (
    <div className="flex gap-2">
      <Input readOnly value={text} aria-label={label} className="h-8 font-mono text-[11px]" />
      <Button variant="outline" size="sm" className="h-8 shrink-0" onClick={() => onCopy(text)}><Copy className="size-3" aria-hidden /> Copy</Button>
    </div>
  );

  return (
    <div className="flex flex-col gap-4">
      <div className="rounded-lg border bg-card p-3">
        <div className="flex items-baseline justify-between border-b pb-2">
          <p className="text-sm text-card-foreground">{state.title}</p>
          <p className="text-[11px] tabular-nums text-chart-1">{stamp(state)}</p>
        </div>
        {preview}
        {state.isolated && (
          <p className="mt-2 text-[11px] text-chart-1">{state.isolated.shown} of {state.isolated.of} series isolated</p>
        )}
      </div>

      <Tabs defaultValue="link">
        <TabsList className="grid h-auto grid-cols-4 gap-2 bg-transparent p-0">
          {MECHANISMS.map((m) => (
            <TabsTrigger key={m.id} value={m.id} className="h-full flex-col items-start gap-1 whitespace-normal rounded-lg border p-2.5 text-left data-[state=active]:border-chart-1">
              <span className="text-xs">{m.label}</span>
              <span className="text-[10px] font-normal leading-snug text-muted-foreground">{m.blurb}</span>
            </TabsTrigger>
          ))}
        </TabsList>

        <TabsContent value="link" className="mt-3 flex flex-col gap-3">
          {copyRow(href, "link")}
          <div className="flex items-center gap-2">
            <Switch id="abs" checked={absolute} onCheckedChange={setAbsolute} disabled={!state.relative} />
            <Label htmlFor="abs" className="text-xs font-normal text-muted-foreground">
              {absolute ? `Locked to ${hhmm(state.range.from)}-${hhmm(state.range.to)} today` : `Opens on "${state.relative}", so it shows a different hour tomorrow`}
            </Label>
          </div>
        </TabsContent>

        <TabsContent value="snapshot" className="mt-3 flex flex-col gap-3">
          <div className="flex items-center gap-2">
            <Switch id="pub" checked={isPublic} onCheckedChange={setPublic} />
            <Label htmlFor="pub" className="text-xs font-normal text-muted-foreground">
              {isPublic ? "Anyone with the link can read this data, no sign-in. Expires in 30 days." : "Only people who can open the dashboard."}
            </Label>
          </div>
          <Button size="sm" className="h-8 self-start" onClick={() => onSnapshot({ isPublic })}>Freeze as of {hhmm(state.range.to)}</Button>
        </TabsContent>

        <TabsContent value="embed" className="mt-3">{copyRow(embed, "embed code")}</TabsContent>

        <TabsContent value="export" className="mt-3 flex gap-2">
          <Button variant="outline" size="sm" className="h-8" onClick={() => onExport({ every: "day" })}>PDF every morning</Button>
          <Button variant="outline" size="sm" className="h-8" onClick={() => onExport({ every: "week" })}>PDF every Monday</Button>
        </TabsContent>
      </Tabs>
    </div>
  );
}

demo.tsxHow it is called: the panel's state, its live URL, and the handlers that copy, freeze and schedule.

import { SharePanel } from "./SharePanel";

/** The checkout errors panel, an hour of eu-west, one series isolated. */
const STATE = {
  title: "Checkout errors",
  range: { from: new Date("2026-09-15T14:00:00Z"), to: new Date("2026-09-15T15:00:00Z") },
  relative: "last 1h",
  filters: ["eu-west"],
  isolated: { shown: 1, of: 6 },
};
const URL = "https://ops.internal/d/checkout?from=2026-09-15T14:00Z&to=2026-09-15T15:00Z&region=eu-west&series=5xx";

/** Six points of the isolated series, drawn small so the stamp has a picture. */
const Preview = () => (
  <svg viewBox="0 0 300 40" className="mt-2 h-10 w-full" aria-hidden>
    <polyline points="4,32 56,24 108,30 160,10 212,16 274,2" fill="none" className="stroke-chart-1" strokeWidth={2} />
  </svg>
);

export default function Demo() {
  return (
    <div className="w-[560px] rounded-lg border bg-background p-4">
      <SharePanel
        state={STATE}
        url={URL}
        preview={<Preview />}
        onCopy={(text) => navigator.clipboard.writeText(text)}
        onSnapshot={(o) => console.log("snapshot", o)}
        onExport={(o) => console.log("export", o)}
      />
    </div>
  );
}
What it renders. Identical markup in both panes, with only the token values changing.

Examples

Screenshots are grouped by product and include the capture date. Dashed boxes highlight this pattern. Notes appear below each screenshot and when you hover over a box.

Netdata

A large collection of system charts with per-chart controls and anomaly-rate views.

Anomalies / Anomaly advisor September 11, 2026 Netdata Agent, Anomaly advisor (public registry node, signed out) medium · dark · desktop-web
Netdata shows the share and count of measurements flagged as unusual in separate charts. This provides a compact overview across many metrics rather than drawing an expected range on each one. People then select a time period to see which metrics contributed. The empty detail panel explains that a selection is needed before results will appear.
  • Anomaly band A separate anomaly-rate chart summarizes many metrics.
  • Explain this metric Definitions appear directly beneath section headings.
  • Cross-filter Selecting a time period reveals the contributing metrics.
  • Empty state The empty panel explains which action will produce results.
  • Share and embed A report control is available above the charts.
Show 1 more example Hide the rest

Kibana

A query bar and filtering controls define the data shown in charts and document tables.

Dashboards / [Flights] Global Flight Dashboard September 10, 2026 Elastic demo environment, sample flight data (guest session) dense · light · desktop-web
The delay table shows percentages without the number of flights behind each one. A high rate based on very few flights would look as prominent as one based on many. A nearby comparison tile names the previous week but does not clearly explain what its percentage represents. The filtering controls are more explicit: a query bar sits above pickers and a price range, giving people both typed and guided ways to narrow the view.
  • Ratio and rate Delay percentages appear without their flight counts.
  • Compare periods The comparison period is named, but the meaning of the percentage is unclear.
  • Share and embed Sharing and full-screen controls are available in the header.
  • Filter bar Pickers and a range control provide alternatives to typing a query.
  • Panel grid Panels use different sizes within a grid.
  • Stacked composition A 100% stack shows the mix of delay types without total volume.
  • Annotation Grouped event markers share the chart’s time axis.
  • Header KPI strip Different tile sizes and arrangements make the summary row less consistent.