Get started
Step 1 - Install the package
npm install @iccandle/selector
Ensure react and react-dom are installed and meet the peer version range.
Step 2 - Host the Charting Library
Copy the TradingView Charting Library build into a path your app can serve as static files (e.g. public/charting_library/ in Vite or Create React App).
Import the library constructor from that path in your bundler setup (see TradingView’s integration docs for your framework). The library is not bundled inside @iccandle/selector; it loads at runtime via library_path (or equivalent) on the widget options.
Step 3 - Bootstrap TradingView and capture the widget instance
Create a ref for the chart DOM node, instantiate the widget in useEffect, store the instance in React state, and remove it on cleanup:
import { useEffect, useRef, useState } from "react";
import type {
ChartingLibraryWidgetOptions,
IChartingLibraryWidget,
ResolutionString,
} from "charting_library/charting_library";
import { widget } from "charting_library/charting_library";
const LIBRARY_PATH = "/charting_library/"; // must match your hosted assets
// Inside your component:
const containerRef = useRef<HTMLDivElement>(null);
const [chartWidget, setChartWidget] = useState<IChartingLibraryWidget | null>(
null,
);
useEffect(
() => {
const el = containerRef.current;
if (!el) return;
const options: ChartingLibraryWidgetOptions = {
container: el,
library_path: LIBRARY_PATH,
symbol: "EURUSD",
interval: "60" as ResolutionString,
datafeed: yourDatafeed,
locale: "en",
autosize: true,
};
const tv = new widget(options);
setChartWidget(tv);
return () => {
try {
tv.remove();
} catch {
/* no-op */
}
setChartWidget(null);
};
},
[
/* library_path, datafeed identity, or other inputs that should recreate the chart */
],
);
You must supply a valid datafeed, symbol, interval, locale, and any other options required by your TradingView license and app. Import paths for widget and types differ by setup (charting_library/charting_library, ./public/charting_library, etc.-follow TradingView’s docs for your bundler).
If your integration only exposes the instance after onChartReady, call setChartWidget inside that callback instead of immediately after new widget(...).
Step 4 - Wrap the chart with SelectorWidget
SelectorWidget must wrap the same subtree that contains the chart container so the scanner overlay positions correctly. Pass the live widget instance (or null while mounting):
import { SelectorWidget } from "@iccandle/selector";
const widgetKey = "icc_search_..."; // your iC Candle key
<SelectorWidget
chartWidget={chartWidget}
widgetKey={widgetKey}
theme="system"
submitCallback={(iframeSrc) => {
/* see Step 5 */
}}
>
<div ref={containerRef} style={{ height: "100%", minHeight: 400 }} />
</SelectorWidget>;
Step 5 - Handle the plugin iframe URL
After a successful scan setup, submitCallback receives a full HTTPS URL for the iC Candle plugin iframe. The query string typically includes the bar window (timestamps / size), symbol, candle_id, apiKey (your widget key), resolved theme, and any active filters-use it as-is in an iframe src or deep link.
Open in a new tab
submitCallback={(iframeSrc) => {
window.open(iframeSrc, "_blank", "noopener,noreferrer");
}}
Show in a modal or side panel
Store the URL in state and render:
{
iframeSrc ? (
<iframe title="iC Candle pattern search" src={iframeSrc} className="..." />
) : null;
}
TypeScript: import SelectorWidgetProps if you wrap SelectorWidget in your own component. Import IChartingLibraryWidget from your Charting Library typings path - this package does not re-export TradingView types.
Theme and remote branding
theme="light"/theme="dark"- forces that palette for the scanner chrome and for values forwarded into the plugin URL.theme="system"(recommended when you don’t control parent theme) - followsprefers-color-schemefor light/dark resolution.
On mount, the widget fetches your org’s tokens from iC Candle and sets CSS custom properties on the widget root (e.g. --iccandle-primary, --iccandle-border). The same resolved theme is reflected in the iframe URL so the plugin UI stays consistent.
Full working example
import { useEffect, useRef, useState } from "react";
import type {
ChartingLibraryWidgetOptions,
IChartingLibraryWidget,
ResolutionString,
} from "charting_library/charting_library";
import { widget } from "charting_library/charting_library";
import { SelectorWidget } from "@iccandle/selector";
const LIBRARY_PATH = "/charting_library/";
const WIDGET_KEY = "icc_search_your48hexcharactershere............";
export function ChartWithIccandleScanner() {
const containerRef = useRef<HTMLDivElement>(null);
const [chartWidget, setChartWidget] = useState<IChartingLibraryWidget | null>(
null,
);
const [pluginSrc, setPluginSrc] = useState("");
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const options: ChartingLibraryWidgetOptions = {
container: el,
library_path: LIBRARY_PATH,
symbol: "EURUSD",
interval: "60" as ResolutionString,
datafeed: yourDatafeed,
locale: "en",
autosize: true,
fullscreen: false,
drawings_access: {
type: "black",
tools: [{ name: "Date Range" }],
},
};
const tv = new widget(options);
setChartWidget(tv);
return () => {
try {
tv.remove();
} catch {
/* no-op */
}
setChartWidget(null);
};
}, []);
return (
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<div style={{ height: 500, width: "100%" }}>
<SelectorWidget
chartWidget={chartWidget}
widgetKey={WIDGET_KEY}
theme="system"
submitCallback={(iframeSrc) => setPluginSrc(iframeSrc)}
>
<div ref={containerRef} style={{ height: "100%", width: "100%" }} />
</SelectorWidget>
</div>
{pluginSrc ? (
<iframe
title="iC Candle search"
src={pluginSrc}
style={{ width: "100%", height: 600, border: "1px solid #ccc" }}
/>
) : null}
</div>
);
}
Replace yourDatafeed and widget options with your real datafeed and TradingView settings. For a concrete in-repo reference (custom datafeed, timezone, visibility handling), see src/tradingview/TradingviewChart.tsx in the [widget-iccandle](https://github.com/iC Candle/widget-iccandle) dev app.
Common patterns
| Pattern | Approach |
|---|---|
| Modal iframe | On submitCallback, set state and render <iframe src={url} /> inside a <dialog>, Radix Dialog, MUI Modal, etc. |
| New tab | window.open(iframeSrc, "_blank", "noopener,noreferrer"). |
| Split layout | Keep the chart in one column and mount the iframe in another when pluginSrc is set (as in the example above). |
| News / events on the time axis | If your datafeed implements getTimescaleMarks, sync marks with localStorage - see Optional: timescale marks. |