qlik-oss/nebula.js/nebula-chart-dev

Claude Code and agent skill by qlik-oss/nebula.js

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Build, develop, and review nebula.js (stardust) visualization charts. Use when: creating supernova charts, implementing hooks (usePromise, useLayout, useStaleLayout, useTheme, useRect, useSelections, useInteractionState), setting up the property panel (ext definition, accordion, items), rendering with React inside a supernova component, handling printing/snapshots, avoiding DOM pollution and global CSS. Covers project scaffolding, data binding, selection handling, theme access, resize handling, and debugging patterns. DO NOT USE for mashup/embed API work or Qlik engine configuration.

Install

npx vskill@latest install qlik-oss/nebula.js/nebula-chart-dev

SKILL.md

Nebula.js Chart Development

Reference: Official quickstart | Examples repo | In-Sense guide | Property panel guide

Multi-File Starter Template

For a real scaffold (not only markdown snippets), use the files in:

  • ./assets/advanced-chart-template/src/index.js
  • ./assets/advanced-chart-template/src/data.js
  • ./assets/advanced-chart-template/src/object-properties.js
  • ./assets/advanced-chart-template/src/ext.js
  • ./assets/advanced-chart-template/src/components/root.jsx
  • ./assets/advanced-chart-template/src/components/AdvancedChart.jsx
  • ./assets/advanced-chart-template/src/components/chart.css
  • ./assets/sample-fixture.fix.js

When asked to generate a new nebula chart in this skill, prefer copying this template folder first, then fill in the TODO sections with chart-specific logic.


Project Structure

Scaffold a new chart:

bash
npx @nebula.js/cli create <name> --picasso none # plain canvas/DOM chart
npx @nebula.js/cli create <name> --picasso minimal # picasso.js scaffold
npx @nebula.js/cli create <name> # full picasso barchart scaffold

Key files generated:

  • src/index.js — supernova entry point (the chart)
  • src/object-properties.js — default QIX object properties
  • src/data.js — data targets (dimensions/measures)
  • src/ext.js — Qlik Sense property panel + environment access

Dev workflow:

bash
npm run start # start nebula dev server with hot reload
npm run build # bundle to /dist
npm run sense # bundle as Qlik Sense extension to /name-ext/

Supernova Skeleton

js
import { useElement, useLayout, useEffect } from '@nebula.js/stardust';
import properties from './object-properties';
import data from './data';
import ext from './ext';
export default function supernova(galaxy) {
return {
qae: { properties, data },
ext: ext(galaxy),
component() {
// All hooks MUST be called here, at the top level — never conditionally
const element = useElement();
const layout = useLayout();
useEffect(() => {
element.innerHTML = `<p>${layout.title}</p>`;
}, [element, layout]);
},
};
}

Rules:

  • Hooks are only valid inside component(). Calling them elsewhere throws "Invalid stardust hook call".
  • Never change hook call order between renders (same rule as React).

Hooks Reference

useElement()

Returns the HTMLElement the chart must render into. Never manipulate the DOM outside this element.

js
const element = useElement();
element.innerHTML = '<p>Hello</p>';
useLayout() vs useStaleLayout()
HookBehavior during selection modal
useLayout()Updates with every layout change; qSelectionInfo.qInSelections becomes true when in selections mode
useStaleLayout()Returns the last non-modal layout — qSelectionInfo.qInSelections is always false

Use `useStaleLayout()` for rendering when you don't want the chart to re-render while a user is actively brushing/selecting (avoids flickering). Use useLayout() only if you specifically need to react to the selection state changing.

js
// Picasso / most charts — render from stale, don't re-render mid-selection
const layout = useStaleLayout();
// Table / charts that must update their highlight state
const layout = useLayout();
useEffect(() => {
if (layout.qSelectionInfo.qInSelections) {
// optionally skip heavy re-render
return;
}
// ...render...
}, [layout]);
usePromise(factory, deps)

Wraps an async operation. The factory must return a Promise. Returns [resolved, rejected].

Why it matters: nebula.js tracks pending promises returned from usePromise to know when the chart has finished rendering. This is critical for:

  • Snapshot/print support (nebula serve --print and snapshooter)
  • Integration test stability
  • Any async data fetch that must complete before the chart is "done"

If async work is done inside a plain useEffect, the framework considers the chart rendered before the data arrives — snapshots will be blank.

js
import { useModel, usePromise, useEffect } from '@nebula.js/stardust';
const model = useModel();
// GOOD — framework waits for this before considering chart "rendered"
const [layout, error] = usePromise(() => model.getLayout(), [model]);
useEffect(() => {
if (!layout) return;
// render with layout
}, [layout]);
js
// BAD for printing — framework does not know to wait
useEffect(() => {
model.getLayout().then((layout) => {
// render...
});
}, [model]);

Pattern: async data transformation with `usePromise`

js
const layout = useLayout();
const [data, err] = usePromise(async () => {
if (!layout?.qHyperCube?.qDataPages[0]?.qMatrix.length) {
throw new Error('No data');
}
// async processing (e.g. fetch enrichment data)
return processRows(layout.qHyperCube.qDataPages[0].qMatrix);
}, [layout]);
useEffect(() => {
if (!data) return;
renderChart(element, data);
}, [data, element]);
useTheme()

Returns a Theme object. The theme re-triggers dependent effects when the active theme changes (e.g. user switches dark/light).

js
import { useTheme, useEffect } from '@nebula.js/stardust';
const theme = useTheme();
useEffect(() => {
const bgColor = theme.getStyle('object', '', 'backgroundColor') ?? '#ffffff';
const fgColor = theme.getContrastingColorTo(bgColor);
element.style.background = bgColor;
element.style.color = fgColor;
}, [theme, element]);

Key `Theme` methods:

MethodReturnsUse case
theme.getStyle(basePath, path, attribute)`string \undefined`Resolve CSS-like token from theme JSON (font size, colors, etc.)
theme.getContrastingColorTo(color)stringFind readable text color for an arbitrary background
theme.getColorPickerColor({ index?, color? })stringResolve a Qlik color-picker value (from color property) to a CSS color
theme.getDataColorPalettes()DataPalette[]Access the theme's named data palettes
theme.getDataColorSpecials()DataColorSpecialsNull/other/tertiary special colors
theme.validateColor(specifier)`string \undefined`Parse any color string incl. Qlik ARGB format

`getStyle` path resolution: Starts at basePath (e.g. "object") and walks upward through ancestors until found.

js
// Font size for chart titles
theme.getStyle('object', 'title.main', 'fontSize');
// Same — attribute dotted notation
theme.getStyle('object', '', 'title.main.fontSize');
// Global font size fallback
theme.getStyle('', '', 'fontSize');

Sense-specific theme vs `useTheme()`: Inside ext (outside component()), galaxy.anything.sense.theme is available but is a different, Sense-specific module. useTheme() is only available inside component() and is always present (local dev and in Sense).

useRect()

Returns { top, left, width, height } of the chart container. Updates automatically via ResizeObserver.

Only use `useRect()` if your chart needs to respond to size changes (e.g. re-layout a canvas or resize a third-party library). Do not add it if the chart already fills the container via CSS.

js
import { useRect, useEffect } from '@nebula.js/stardust';
const rect = useRect();
// Only re-render when dimensions change
useEffect(() => {
if (!instance) return;
instance.resize(rect.width, rect.height);
instance.update();
}, [rect.width, rect.height, instance]);

Note: ResizeObserver must be polyfilled by the consumer app if older browser support is required.

useSelections()
js
import { useSelections, useElement, useState, useEffect } from '@nebula.js/stardust';
const selections = useSelections();
const [selectedRows, setSelectedRows] = useState([]);
// Add click listener (register in one effect, keeping it stable)
useEffect(() => {
const onClick = (e) => {
if (e.target.tagName !== 'TD') return;
if (!selections.isActive()) {
selections.begin('/qHyperCubeDef');
}
const row = +e.target.parentElement.getAttribute('data-row');
setSelectedRows((prev) => (prev.includes(row) ? prev.filter((v) => v !== row) : [...prev, row]));
};
element.addEventListener('click', onClick);
return () => element.removeEventListener('click', onClick);
}, [element]);
// Commit or clear selections when rows change
useEffect(() => {
if (selections.isActive()) {
if (selectedRows.length) {
selections.select({ method: 'selectHyperCubeCells', params: ['/qHyperCubeDef', selectedRows, []] });
} else {
selections.select({ method: 'resetMadeSelections', params: [] });
}
} else if (selectedRows.length) {
setSelectedRows([]);
}
}, [selections.isActive(), selectedRows]);
useInteractionState() (preferred over deprecated useConstraints())
js
import { useInteractionState } from '@nebula.js/stardust';
const interactions = useInteractionState();
// interactions.active — true = user can click/scroll
// interactions.select — true = selections allowed
// interactions.passive — true = tooltips allowed
// interactions.edit — true = edit mode active
useEffect(() => {
if (!interactions.active) return; // don't add listeners in passive/view mode
element.addEventListener('click', handler);
return () => element.removeEventListener('click', handler);
}, [interactions.active]);
Other hooks (quick reference)
HookReturns
useModel()QIX GenericObject API
useApp()QIX Doc API
useAppLayout()App-level layout (locale info, etc.)
useTranslator()Translator for i18n strings
useDeviceType()"desktop" or "touch"
useState(initial)[value, setter] — triggers re-render
useMemo(fn, deps)Memoized value
useRef(initial)Stable mutable ref (no re-render)
useAction(fn, deps)Register a toolbar action button

Rendering with React

Nebula's component() is not React — it uses its own hooks system. To use React for your chart UI, mount/unmount a React tree from useEffect.

js
// src/components/root.jsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import MyChart from './MyChart';
export function render(element, props) {
const root = createRoot(element);
root.render(<MyChart {...props} />);
return root;
}
export function teardown(root) {
root.unmount();
}
js
// src/index.js
import { useElement, useLayout, useEffect } from '@nebula.js/stardust';
import { render, teardown } from './components/root';

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
87
Repository forks
56
7-day trend score
22

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/qlik-oss/nebula.js/nebula-chart-dev/badge)](https://verified-skill.com/skills/qlik-oss/nebula.js/nebula-chart-dev)

Something wrong with this skill or its page? Tell us.