Agent Skills

grafana-scenes

frontendgrafana3.5K installs

Build Grafana plugin pages using the @grafana/scenes framework. Use when creating new scene pages, adding panels/visualizations, setting up drilldown navigation, defining variables, configuring query runners, building table/timeseries/stat panels, or extending SceneObjectBase for custom scene objects. Triggers on any work involving SceneApp, SceneAppPage, EmbeddedScene, SceneQueryRunner, SceneDataTransformer, PanelBuilders, SceneFlexLayout, QueryVariable, or drilldown/tab configuration in Grafan

Install

npx skills add https://github.com/grafana/skills --skill grafana-scenes
SKILL.md

@grafana/scenes Framework

Build reactive, data-driven Grafana plugin pages with declarative scene objects.

Core Concepts

Scenes composes a tree of objects: SceneApp → SceneAppPage → EmbeddedScene → layouts → panels. Each node can own data ($data), variables ($variables), time ranges ($timeRange), and behaviors ($behaviors) that propagate down the tree.

Quick Start: New Scene Page

1. Create the scene file

// src/components/scenes/MyFeature/scene.tsx
import {
  EmbeddedScene, SceneFlexLayout, SceneFlexItem,
  SceneQueryRunner, SceneVariableSet, QueryVariable,
  PanelBuilders, VariableValueSelectors, SceneControlsSpacer,
} from '@grafana/scenes';

export function getMyFeatureScene(params: { datasource: DataSourceRef }) {
  const queryRunner = new SceneQueryRunner({
    datasource: params.datasource,
    queries: [{ refId: 'A', expr: 'up{cluster=~"$cluster"}', instant: true, format: 'table' }],
  });

  const panel = PanelBuilders.table()
    .setData(queryRunner)
    .setTitle('My Table')
    .build();

  return new EmbeddedScene({
    $variables: new SceneVariableSet({
      variables: [
        new QueryVariable({
          name: 'cluster',
          query: 'label_values(up, cluster)',
          datasource: params.datasource,
          isMulti: true, includeAll: true, defaultToAll: true,
        }),
      ],
    }),
    controls: [new VariableValueSelectors({}), new SceneControlsSpacer()],
    body: new SceneFlexLayout({
      direction: 'column',
      children: [new SceneFlexItem({ body: panel })],
    }),
  });
}

2. Create the page

// src/components/scenes/MyFeature/MyFeature.tsx
import { SceneAppPage, SceneTimeRange } from '@grafana/scenes';

export function getMyFeaturePage(params) {
  return new SceneAppPage({
    title: 'My Feature',
    url: '/a/my-plugin-id/my-feature',
    routePath: 'my-feature/*',
    $timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }),
    getScene: () => getMyFeatureScene(params),
    drilldowns: [],
  });
}

3. Register in SceneApp

Add the page to the SceneApp pages array in the root scene file.

Key Patterns

Drilldowns (click-through navigation)

drilldowns: [{
  routePath: ':cluster/*',
  getPage: (match, parent) => new SceneAppPage({
    title: decodeURIComponent(match.params.cluster),
    url: `${parent.state.url}/${match.params.cluster}`,
    routePath: `${match.params.cluster}/*`,
    getScene: () => detailScene(decodeURIComponent(match.params.cluster)),
  }),
}]

Tabs (sub-pages within a detail view)

Pass tabs: [SceneAppPage, ...] instead of getScene on a SceneAppPage. Each tab is itself a SceneAppPage with its own scene.

Query with transformations

Wrap a SceneQueryRunner in SceneDataTransformer to apply Grafana transforms or custom RxJS operators:

new SceneDataTransformer({
  $data: queryRunner,
  transformations: [
    { id: 'organize', options: { renameByName: { 'Value #A': 'CPU' } } },
    (ctx) => (source) => source.pipe(map((frames) => /* custom transform */)),
  ],
})

Custom scene object

Extend SceneObjectBase with a static Component for custom interactive UI:

class MyWidget extends SceneObjectBase<MyWidgetState> {
  static Component = ({ model }: SceneComponentProps<MyWidget>) => {
    const state = model.useState();
    return <div>{state.value}</div>;
  };
}

Table column overrides

Build ConfigOverrideRule objects for drill-down links, filtering, units, widths, custom cells.

Panel types

PanelBuilders.table(), .timeseries(), .stat(), .gauge(), .barchart() — chain .setData(), .setTitle(), .setUnit(), .setOption(), .setOverrides(), then .build().

Common Pitfalls

  • Always use routePath: 'path/*' (with wildcard) on pages that have drilldowns or tabs
  • encodeURIComponent/decodeURIComponent URL params — K8s names can contain /
  • Variables referenced in queries as $varName must exist in an ancestor SceneVariableSet
  • getScene is called lazily; don't create side effects in the factory
  • For instant queries, set both instant: true and format: 'table'

Resources

Related skills

vercel-react-best-practicesvercel-labs751KReact and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.vercel-composition-patternsvercel-labs361KReact composition patterns that scale. Use when refactoring components with boolean prop proliferation, building flexible component libraries, or designing reusable APIs. Triggers on tasks involving compound components, render props, context providers, or component architecture. Includes React 19 API changes.vercel-react-view-transitionsvercel-labs135KGuide for implementing smooth, native-feeling animations using React's View Transition API (`<ViewTransition>` component, `addTransitionType`, and CSS view transition pseudo-elements). Use this skill whenever the user wants to add page transitions, animate route changes, create shared element animations, animate enter/exit of components, animate list reorder, implement directional (forward/back) navigation animations, or integrate view transitions in Next.js. Also use when the user mentions viewpick-ui-libraryemilkowalski124KPick the right library for a given frontend task from a curated, opinionated list — numbers, OTP inputs, charts, command menus, virtualization, drag and drop, toasts, state, styling, and more. Only runs when explicitly invoked; it does not trigger on its own.

Search skills and MCP servers

Fuzzy search across 23,137 skills and servers