Lightweight Charts React Components
GitHubOfficialCreated by ukorvlDistributed by GitHub
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a set of React components and recipes for integrating TradingView's lightweight-charts library into React applications. It simplifies the management of chart lifecycles, series creation, and data updates. The library supports various chart types including candlestick, line, and area series, along with advanced features like real-time WebSocket updates, synchronized charts, and custom tooltips. It is designed to handle complex financial data requirements such as infinite history scrolling, indicator panes, and trade markers while maintaining React's declarative programming model.
What it does
- Provides React components for rendering TradingView lightweight-charts.
- Manages chart and series lifecycle, including creation and cleanup.
- Supports real-time data updates via WebSocket integration.
- Enables complex UI features like crosshair legends, tooltips, and trade markers.
- Facilitates synchronized multi-chart layouts and custom axis configurations.
- Handles infinite history loading and viewport management.
When to use it
- When building financial or trading dashboards in a React application.
- When you need to integrate TradingView lightweight-charts with React state.
- When implementing features like real-time price updates or historical data pagination.
- When creating complex chart layouts with indicators, volume panes, or synchronized crosshairs.
Advantages
- Declarative component-based API for chart configuration.
- Automatic management of chart lifecycle and DOM cleanup.
- Built-in recipes for common financial charting patterns.
- Flexible data handling for both static datasets and live streaming updates.
Usage examples
- “How do I create a basic line chart using lightweight-charts-react-components?”
- The agent provides a code snippet using the Chart and LineSeries components, demonstrating how to pass data and configure container dimensions.
- “How can I implement a tooltip that shows OHLC values on hover?”
- The agent provides a recipe using onCrosshairMove to track cursor position and map it to the series data, rendering a custom React tooltip.
- “How do I handle infinite scrolling for historical data?”
- The agent provides a pattern for using a ref-based data store and a loadOlder callback to prepend data to the chart without losing the user's current view.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/github/lightweight-charts-react-components/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip lightweight-charts-react-components.zip -d .agents/skills
Documentation by ukorvl
lightweight-charts-react-components guide
Quick Start
Let the components manage chart and series creation and cleanup. Import components
and ref types from lightweight-charts-react-components; import native data, option,
and event types from lightweight-charts.
Check versions against the compatibility table below. Read declared ranges from the app's
package.json, then the installed versions from theversionfield ofnode_modules/<package>/package.jsonforlightweight-charts-react-components,lightweight-charts,react, andreact-dom. If the library is not installed, follow Installation. Ensure chart rendering runs in the browser, within the app's client boundary.For a basic static chart, start from the minimal chart below. Otherwise, read the reference matching the requested behavior. Adapt its feed and UI; preserve its data contract, component nesting, and API readiness handling.
Choose one data owner: pass the complete dataset through
datafor React-owned updates, or usereactive={false}and the series API for frequent ticks.
Installation
Install the wrapper together with its lightweight-charts peer dependency, using
the project's package manager. React and React DOM must already be installed.
npm install lightweight-charts-react-components lightweight-chartsMinimal chart
import type { LineData, UTCTimestamp } from "lightweight-charts";
import {
Chart,
LineSeries,
TimeScale,
TimeScaleFitContentTrigger,
} from "lightweight-charts-react-components";
// Data must be sorted ascending by time with unique times (UTC seconds here).
export function SimpleLineChart({ data }: { data: LineData<UTCTimestamp>[] }) {
return (
// autoSize follows the container, which needs a nonzero height.
<Chart
options={{ autoSize: true }}
containerProps={{ style: { height: 300, width: "100%" } }}
>
<LineSeries data={data} />
<TimeScale>
<TimeScaleFitContentTrigger deps={[]} />
</TimeScale>
</Chart>
);
}Examples and snippets (reference files)
Read the reference matching the user's outcome. Each contains a complete TypeScript recipe, data and integration contracts, reasons for the architecture, failure modes, and acceptance checks. Combine references when a task spans several outcomes; do not load every reference for a simple chart.
| User request | Reference |
|---|---|
| WebSocket candles, updating the current bar, stale history, excessive chart renders | Market data and live updates |
| Older history on scroll, viewport jumps, symbol/interval changes, late API responses | History and range selection |
| Candles plus volume, moving averages, oscillator panes, independent price scales | Price, volume, and indicators |
| OHLC legend, hover details, missing data, tooltip positioning | Hover legends and tooltips |
| Executions and signals, stop/target levels, click-to-place price lines | Trade markers and price levels |
| Compare assets with different prices, fixed-baseline returns, toggle comparisons | Performance comparison |
| Separate charts that share scrolling, zooming, and a crosshair | Synchronized charts |
| Trading-session shading, pane backgrounds, custom drawing that follows zoom | Session overlays and primitives |
| Options strikes, yield maturities, choosing a numeric or custom horizontal axis | Numeric axes and chart types |
If no reference matches the request, tell the user that the skill has no recipe
for it. Then build the solution from the public components and the upstream
lightweight-charts API, applying the Quick Start rules and the edge cases below.
Skill compatibility
The recipes are verified against the reference versions below. These are the tested baseline, not the latest available releases.
If an installed version differs from the reference version, compare it with the compatibility range. Inside the range, recipes apply as written. Outside it, tell the user about the mismatch before writing code, because breaking API changes can affect the recipes, and verify every API the recipe uses against the installed package.
| Dependency or environment | Reference version | Compatibility range and checks |
|---|---|---|
lightweight-charts-react-components | 2.6.1 | Recipe target: >=2.6.1 <3. Other versions require checking installed exports, chart API refs, and reactive data behavior before adapting a recipe. |
lightweight-charts | 5.2.1 | Declared peer: >=5.0.8 <6. |
| React and React DOM | 19.2.6 | Declared peers: >=18.2 <20. Keep react and react-dom on matching versions and use the application's existing React instance. |
| Node.js | — | Package engines: >=18.14.0. The app's build tools can require a newer version. |
| Module system | ESM | ESM import entry only; there is no CommonJS require entry. |
| Rendering | Browser | Charts need the DOM and canvas. Render them on the client, never during server rendering. |
Common edge cases
Chart sizing: Put DOM styles on
containerPropsand give the chart a nonzero height.Required ancestors: Headless chart children need a chart wrapper ancestor.
Markers,PriceLine, andSeriesPrimitivebelong inside their series;PriceScale,Watermark, andPanePrimitiverequire aPane;TimeScaleFitContentTriggerrequires aTimeScale.API refs and readiness: Use
ref.current?.api()for the native API andcontainerReffor the div. ForChart, useChartApiRef<Time, IChartApi>..api()can initially return null; follow the recipe's readiness step before imperative setup.Viewport resets: To preserve scroll/zoom, keep the chart's key stable and live/history updates out of fit-trigger dependencies. Fit on initial load or a deliberate reset. See range handling.
Data updates: Use
datafor React-owned updates, orreactive={false}and the series API when per-tick React renders are measurably costly. Avoid mixing both. Reactivedatais always the complete dataset. With wrapper 2.6.1 and Lightweight Charts 5.2.1,series.data()reconstructs candle and line objects; the wrapper's earlier-item reference comparison therefore sends multi-bar tail updates throughsetData(), even when React keeps earlier references stable. Keep data immutable, but use the chart-ownedreactive={false}recipe and explicitseries.update(bar)when incremental writes are required. See live updates.
Source
Adapted from ukorvl/lightweight-charts-react-components
at commit 0368191127568e48aef6f7bda2ff3900e8e24756. Original author: ukorvl.
The MIT license and all nine reference recipes are retained. Local
corrections in live updates reject
malformed timestamps and explain the tested versions' reactive-update limitation;
session overlays clarifies the
primitive update callback.
The other seven references and MIT notice are unchanged. This catalog copy adds
source attribution and operational boundaries; it does not imply TradingView
endorsement.
Recipes generate application code and can change dependencies. Follow the user's requested scope and the host's approval policy. Feed access belongs to the app's separately authorized adapter; keep credentials out of client code. Trade markers are visual annotations, not order execution or server-side alerts. Use synthetic data for local checks and report browser/feed behavior that remains untested.
Files
- ATTRIBUTION.mdAdded by AutoGineer1.0 KiB
- LICENSE1.0 KiB
- SKILL.md10.1 KiB
- references/history-and-range-selection.md11.5 KiB
- references/hover-legends-and-tooltips.md8.1 KiB
- references/market-data-and-live-updates.md11.9 KiB
- references/numeric-axes-and-chart-types.md10.3 KiB
- references/performance-comparison.md7.2 KiB
- references/price-volume-and-indicators.md9.9 KiB
- references/session-overlays-and-primitives.md10.1 KiB
- references/synchronized-charts.md8.7 KiB
- references/trade-markers-and-price-levels.md10.1 KiB
references/trade-markers-and-price-levels.md
Trade markers and price levels
Use this for execution history, strategy signals, entry/stop/target overlays, or click-to-place horizontal levels. These are visual annotations. The chart does not execute orders, persist edits, or turn a marker into a drawing tool.
Choose the annotation by what it represents
| Requirement | Primitive | Ownership |
|---|---|---|
| An execution or signal at a candle | Markers | Child of the candle series |
| A horizontal entry, stop, target, or alert level | PriceLine | Child of the series whose price scale it uses |
| A vertical event line, region, or arbitrary custom drawing | SeriesPrimitive / PanePrimitive | See session overlays |
Keep domain IDs stable. A level's React key must be its ID, not its changing price,
so edits update an existing price line. Marker order must be ascending by time;
multiple markers can belong to one candle. Markers expects the complete list
on every reactive change, including [] to clear it.
Recipe: executions, editable levels, and click-to-place alerts
candles is an ascending unique UTC-second snapshot. Each execution's barTime
already identifies its containing candle on that exact timeline. The market-data
adapter must map an execution instant to a bar using the venue's interval and
session rules. A naive local-time rounding operation is unsafe for session-based
bars. Events outside loaded history are retained upstream and become visible
when that history is loaded.
import { useCallback, useMemo, useRef, useState } from "react";
import { LineStyle } from "lightweight-charts";
import type {
CandlestickData,
MouseEventParams,
SeriesMarker,
UTCTimestamp,
} from "lightweight-charts";
import {
CandlestickSeries,
Chart,
Markers,
Pane,
PriceLine,
TimeScale,
TimeScaleFitContentTrigger,
type SeriesApiRef,
} from "lightweight-charts-react-components";
type Execution = {
id: string;
barTime: UTCTimestamp;
side: "buy" | "sell";
quantity: number;
};
type Level = { id: string; price: number; title: string; color: string };
const quantize = (rawPrice: number, tickSize: number, precision: number) =>
Number((Math.round(rawPrice / tickSize) * tickSize).toFixed(precision));
function PriceInput({
price,
tickSize,
precision,
onCommit,
}: {
price: number;
tickSize: number;
precision: number;
onCommit: (price: number) => void;
}) {
// Hold the typed text and round on commit. Rounding each keystroke would rewrite
// the field mid-entry: with a tick of 5, typing "105" would turn "1" into 0.
const [draft, setDraft] = useState<string | null>(null);
const commit = () => {
if (draft === null) return;
setDraft(null);
const rawPrice = Number(draft);
if (draft !== "" && Number.isFinite(rawPrice)) {
onCommit(quantize(rawPrice, tickSize, precision));
}
};
return (
<input
type="number"
step={tickSize}
value={draft ?? price}
onChange={event => setDraft(event.target.value)}
onBlur={commit}
onKeyDown={event => {
if (event.key === "Enter") commit();
}}
/>
);
}
export function TradeAnnotations({
candles,
executions,
initialLevels,
tickSize,
precision,
}: {
candles: CandlestickData<UTCTimestamp>[];
executions: Execution[];
initialLevels: Level[];
tickSize: number;
precision: number;
}) {
const seriesRef = useRef<SeriesApiRef<"Candlestick">>(null);
const [levels, setLevels] = useState(initialLevels);
const [showExecutions, setShowExecutions] = useState(true);
const [placing, setPlacing] = useState(false);
const markers = useMemo<SeriesMarker<UTCTimestamp>[]>(() => {
if (!showExecutions) return [];
const loadedTimes = new Set(candles.map(bar => bar.time));
return executions
.filter(execution => loadedTimes.has(execution.barTime))
.map(execution => ({
id: execution.id,
time: execution.barTime,
position:
execution.side === "buy" ? ("belowBar" as const) : ("aboveBar" as const),
shape: execution.side === "buy" ? ("arrowUp" as const) : ("arrowDown" as const),
color: execution.side === "buy" ? "#16a34a" : "#dc2626",
text: `${execution.side.toUpperCase()} ${execution.quantity}`,
}))
.sort((a, b) => a.time - b.time || a.id.localeCompare(b.id));
}, [candles, executions, showExecutions]);
const onClick = useCallback(
(param: MouseEventParams) => {
if (!placing || !param.point || !(tickSize > 0) || !Number.isFinite(tickSize))
return;
const series = seriesRef.current?.api();
if (!series || param.paneIndex !== series.getPane().paneIndex()) return;
// Convert from pane-local pixels using the target series' actual scale.
// point.y is not a price and may refer to an unrelated pane.
const rawPrice = series.coordinateToPrice(param.point.y);
if (rawPrice === null || !Number.isFinite(rawPrice)) return;
const price = quantize(rawPrice, tickSize, precision);
const id = crypto.randomUUID();
setLevels(previous => [
...previous,
{ id, price, title: "Alert", color: "#f59e0b" },
]);
setPlacing(false);
},
[placing, tickSize, precision]
);
return (
<section>
<button onClick={() => setShowExecutions(value => !value)}>
{showExecutions ? "Hide executions" : "Show executions"}
</button>
<button onClick={() => setPlacing(value => !value)}>
{placing ? "Cancel placement" : "Place alert level"}
</button>
<Chart
options={{ autoSize: true }}
containerProps={{ style: { height: 360 } }}
onClick={onClick}
>
<Pane>
<CandlestickSeries
ref={seriesRef}
data={candles}
options={{
priceLineVisible: false,
priceFormat: { type: "price", minMove: tickSize, precision },
}}
>
<Markers markers={markers} options={{ zOrder: "normal" }} />
{levels.map(level => (
<PriceLine
key={level.id}
price={level.price}
options={{
title: level.title,
color: level.color,
lineStyle: LineStyle.Dashed,
axisLabelVisible: true,
}}
/>
))}
</CandlestickSeries>
</Pane>
<TimeScale>
<TimeScaleFitContentTrigger deps={[]} />
</TimeScale>
</Chart>
{levels.map(level => (
<label key={level.id} style={{ display: "block" }}>
{level.title}
<PriceInput
price={level.price}
tickSize={tickSize}
precision={precision}
onCommit={price =>
setLevels(previous =>
previous.map(item => (item.id === level.id ? { ...item, price } : item))
)
}
/>
<button
onClick={() =>
setLevels(previous => previous.filter(item => item.id !== level.id))
}
>
Remove
</button>
</label>
))}
</section>
);
}Validate tickSize as a positive finite number and precision as the instrument's
supported nonnegative integer before rendering. The rounding above is display
rounding for a tick grid starting at zero. Instruments with different price-band
rules need the venue-specific quantizer; do not reuse this as an order validator.
initialLevels is a mount-time seed. Mount by instrument ID or replace it with a
controlled levels/onLevelsChange contract when annotation state lives upstream.
Lifecycle and adaptation details
PriceLine reacts to both price and options changes and removes its upstream
line when unmounted. The explicit price prop is separate from options; do
not put the price only inside options. Disabling the candle series' default last
price line reduces visual confusion with domain levels.
Markers creates the upstream plugin and updates marker lists reactively, but
the current hook does not apply new options after creation. To change a plugin
option such as zOrder, remount Markers with a key identifying that option or
call markersRef.current?.api()?.applyOptions(...). The latter requires a
MarkersApiRef and initialized API. Showing/hiding the list uses markers={[]}
without recreating the plugin.
For marker click details, MouseEventParams.hoveredObjectId can identify a marker
whose id was supplied. Resolve it against application execution IDs. For
draggable lines or regions, implement interaction and hit testing in a drawing
plugin; PriceLine alone has no drag callbacks.
Keep persistence and network changes in the application's annotation store. Changing local chart state should not imply placing a live order or registering an alert on a server. Add the application's explicit save/submit flow if requested.
Acceptance checks
Add two executions to the same candle, reorder input, and verify sorted markers remain. Hide them and verify the complete list is cleared; show them again.
With a tick of 5, type 105 into a level's price: the field keeps each digit, and the line moves on blur or Enter. Clear the field and blur: the price is restored.
Edit a level's price and title without changing its ID. Remove it and verify the line disappears. Change instrument and verify annotations do not leak across it.
In placement mode, click the price pane at a known price. Verify tick rounding. Add another pane and verify clicks there do not create levels on the price series.
Change marker options and verify the chosen remount/API path actually applies them; simply changing the options prop is insufficient in the current hook.
Requirements and compatibility
Written for: GitHub Copilot
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- ukorvl
- Publisher
- GitHub (Official)
- Distributed by
- GitHub
- Source
- github/awesome-copilot · skills/lightweight-charts-react-components · commit 797337f
- Licence
- MIT License (redistribution allowed)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by ukorvl or GitHub.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download lightweight-charts-react-components.zip