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.
Related
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.
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.
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®ion=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.
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.
Anomalies / Anomaly advisor September 11, 2026Netdata 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 bandA separate anomaly-rate chart summarizes many metrics.
Dashboards / [Flights] Global Flight Dashboard September 10, 2026Elastic 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 rateDelay percentages appear without their flight counts.
Compare periodsThe comparison period is named, but the meaning of the percentage is unclear.
Share and embedSharing and full-screen controls are available in the header.
Filter barPickers and a range control provide alternatives to typing a query.
Panel gridPanels use different sizes within a grid.
Stacked compositionA 100% stack shows the mix of delay types without total volume.
AnnotationGrouped event markers share the chart’s time axis.
Header KPI stripDifferent tile sizes and arrangements make the summary row less consistent.