
The Settings panel is where most users form their first impression of your Pine Script indicator. A script with thirty unorganized inputs scattered across a single flat list feels intimidating and amateurish. A script with the same thirty inputs organized into logical groups, with irrelevant settings automatically grayed out and helpful tooltips on every field, feels professional and trustworthy. The difference is entirely in how you design the input UI.
Pine Script v6 introduced several features that make professional input design possible1. Enum dropdowns replace fragile string comparisons. The active parameter creates conditional visibility where related settings dim when their parent toggle is off. Groups and inline parameters organize inputs into clean sections. This guide walks through each of these features and finishes with a complete example of a professional strategy settings panel.
Script adoption on TradingView depends heavily on usability. Two scripts with identical logic will see very different adoption rates if one has clean, organized settings and the other has a wall of unstructured inputs. Good input design accomplishes three things.
The v6 features covered in this guide require no advanced programming knowledge. They are all parameters on the standard input functions you already use.
In Pine Script v5, dropdown menus required input.string() with an options list of raw strings2. Your code then compared the selected string to string literals, which was error-prone and cluttered.
// v5 approach - fragile string comparisons
string maTypeInput = input.string("SMA", "MA Type", options = ["SMA", "EMA", "WMA", "VWMA"])
float maResult = maTypeInput == "SMA" ? ta.sma(close, 20) :
maTypeInput == "EMA" ? ta.ema(close, 20) :
maTypeInput == "WMA" ? ta.wma(close, 20) :
ta.vwma(close, 20)
The problem is that every comparison uses a raw string literal. If you misspell "SMA" as "sma" in one comparison, the logic breaks silently. There is no compiler check to catch the error.
Pine Script v6 enums define a named set of valid values1. The input.enum() function creates a dropdown that maps directly to enum members3. All comparisons use named constants, so typos produce compiler errors instead of silent bugs.
//@version=6
indicator("Enum dropdown demo", overlay = true)
//@enum Moving average type options for the indicator.
//@field sma Simple Moving Average
//@field ema Exponential Moving Average
//@field wma Weighted Moving Average
//@field vwma Volume Weighted Moving Average
enum MAType
sma = "Simple (SMA)"
ema = "Exponential (EMA)"
wma = "Weighted (WMA)"
vwma = "Volume Weighted (VWMA)"
MAType maTypeInput = input.enum(MAType.sma, "MA Type")
int maLenInput = input.int(20, "MA Length", minval = 1)
float maResult = switch maTypeInput
MAType.sma => ta.sma(close, maLenInput)
MAType.ema => ta.ema(close, maLenInput)
MAType.wma => ta.wma(close, maLenInput)
MAType.vwma => ta.vwma(close, maLenInput)
plot(maResult, "MA", color.blue, 2)
The field titles in the enum declaration are the strings displayed in the dropdown. The field names (sma, ema, wma, vwma) are the named constants used in your code logic. If you write MAType.smaa by mistake, the compiler catches it immediately. The switch statement covers every possible value with no ambiguity.
You can limit which enum members appear in the dropdown by passing an options argument to input.enum()3. This is useful when the same enum is shared across multiple scripts but a specific script only supports a subset of the options.
MAType maTypeInput = input.enum(MAType.sma, "MA Type", options = [MAType.sma, MAType.ema])
Now only the Simple and Exponential options appear in the dropdown, even though the enum defines four members.
The group parameter on any input function places that input into a named section in the Settings panel3. The section heading appears as collapsible text, and all inputs with the same group string appear together regardless of their order in the source code.
//@version=6
indicator("Grouped inputs demo", overlay = true)
// --- Moving Average Group ---
string GRP_MA = "Moving Average"
MAType maTypeInput = input.enum(MAType.sma, "Type", group = GRP_MA)
int maLenInput = input.int(20, "Length", minval = 1, group = GRP_MA)
color maClrInput = input.color(color.blue, "Color", group = GRP_MA)
// --- Bollinger Bands Group ---
string GRP_BB = "Bollinger Bands"
bool showBBInput = input.bool(true, "Show Bands", group = GRP_BB)
float bbMultInput = input.float(2.0, "Multiplier", minval = 0.5, step = 0.5, group = GRP_BB)
color bbClrInput = input.color(color.gray, "Band Color", group = GRP_BB)
Storing the group string in a constant variable (GRP_MA, GRP_BB) ensures consistency. If you need to rename a section later, you change it in one place. The group string is case-sensitive, so "Moving Average" and "moving average" would create two separate sections.
The inline parameter places multiple inputs on the same line in the Settings panel3. This is ideal for pairs of related settings like a value and its color, or a length and its source.
int maLenInput = input.int(20, "MA", inline = "ma_line", group = GRP_MA)
color maClrInput = input.color(color.blue, "", inline = "ma_line", group = GRP_MA)
Both inputs share the inline string "ma_line", so they appear on the same row. The color input uses an empty title because the label from the length input already identifies the line. The particular string used for inline is arbitrary and does not appear in the UI. It only needs to match across inputs that belong on the same line.
When using inline, input fields sit immediately to the right of their labels rather than being left-aligned in a column. If you have multiple inline rows with labels of different lengths, the fields will not align vertically. You can fix this by padding shorter labels with Unicode EN spaces (U+2002) to match the width of the longest label.
The active parameter is the single most impactful feature for professional input design1. When active is false, the input appears grayed out and users cannot change its value. When active is true (the default), the input works normally3. Because active accepts an "input bool" argument, you can tie it directly to the value of another input.
//@version=6
indicator("Active inputs demo", overlay = true)
// --- Smoothing toggle controls child inputs ---
bool showSmoothInput = input.bool(false, "Enable Smoothing", group = "Smoothing")
int smoothLenInput = input.int(10, "Smoothing Length", minval = 1, group = "Smoothing", active = showSmoothInput)
color smoothClrInput = input.color(color.orange, "Smoothing Color", group = "Smoothing", active = showSmoothInput)
float rawMA = ta.sma(close, 20)
float smoothedMA = showSmoothInput ? ta.sma(rawMA, smoothLenInput) : rawMA
plot(smoothedMA, "MA", showSmoothInput ? smoothClrInput : color.blue, 2)
When "Enable Smoothing" is unchecked, the length and color inputs below it are grayed out. Users can see they exist but cannot interact with them until they check the parent toggle. This immediately communicates which settings are relevant to the current configuration.
The most powerful pattern combines enum dropdowns with active to create mode-based settings. Selecting a mode from the dropdown reveals only the inputs relevant to that mode.
//@version=6
indicator("Mode-based inputs demo", overlay = true)
//@enum Strategy modes with descriptive titles.
//@field trend Trend-following mode using moving average crossovers.
//@field mean Mean reversion mode using Bollinger Band extremes.
//@field breakout Breakout mode using Donchian channel violations.
enum StrategyMode
trend = "Trend Following"
mean = "Mean Reversion"
breakout = "Breakout"
StrategyMode modeInput = input.enum(StrategyMode.trend, "Strategy Mode", group = "Mode")
// --- Trend Following Settings ---
string GRP_TREND = "Trend Following Settings"
int fastMAInput = input.int(10, "Fast MA Length", minval = 1, group = GRP_TREND, active = modeInput == StrategyMode.trend)
int slowMAInput = input.int(50, "Slow MA Length", minval = 1, group = GRP_TREND, active = modeInput == StrategyMode.trend)
// --- Mean Reversion Settings ---
string GRP_MEAN = "Mean Reversion Settings"
int bbLenInput = input.int(20, "BB Length", minval = 1, group = GRP_MEAN, active = modeInput == StrategyMode.mean)
float bbMultInput = input.float(2.0, "BB Multiplier", minval = 0.5, step = 0.5, group = GRP_MEAN, active = modeInput == StrategyMode.mean)
// --- Breakout Settings ---
string GRP_BRK = "Breakout Settings"
int donchianLenInput = input.int(20, "Channel Length", minval = 1, group = GRP_BRK, active = modeInput == StrategyMode.breakout)
bool trailStopInput = input.bool(true, "Use Trailing Stop", group = GRP_BRK, active = modeInput == StrategyMode.breakout)
When the user selects "Trend Following" from the mode dropdown, only the fast and slow MA inputs are active. The Bollinger Band and Donchian Channel inputs are all grayed out. Switch to "Mean Reversion" and the BB settings activate while everything else dims. This pattern scales to any number of modes and keeps the settings panel clean regardless of how many total inputs the script contains.
The tooltip parameter adds a question mark icon next to the input field. Hovering over it displays the tooltip text. This is where you explain what the input does, what valid ranges are, and any important caveats.
int maLenInput = input.int(20, "MA Length", minval = 1,
tooltip = "Number of bars for the moving average calculation.\nHigher values produce smoother results but lag more.\nRecommended range: 10-200.")
Tooltips support newline characters with \n for formatting. The tooltip text can be up to 40,960 characters long, which is the same limit as input.string() and input.text_area() text fields3. Use this space to document complex parameters thoroughly. A well-written tooltip eliminates the need for a separate documentation page.
When multiple inputs share the same inline row, the tooltip icon appears to the right of the last input on that line. Only the tooltip from the last input in the row is displayed4. Plan your inline groupings accordingly.
This example combines every pattern discussed above into a single indicator with a professional-grade settings panel. It implements a multi-mode moving average indicator with optional Bollinger Bands, signal markers, and a dashboard table.
//@version=6
indicator("Professional Settings Demo", overlay = true)
// =============================================
// ENUMS
// =============================================
//@enum Moving average calculation methods.
enum MAMethod
sma = "Simple (SMA)"
ema = "Exponential (EMA)"
wma = "Weighted (WMA)"
vwma = "Volume Weighted (VWMA)"
//@enum Signal display modes.
enum SignalDisplay
arrows = "Arrow Markers"
colors = "Bar Colors"
off = "Off"
// =============================================
// INPUTS - Main Settings
// =============================================
string GRP_MAIN = "Main Settings"
MAMethod maMethodInput = input.enum(MAMethod.sma, "MA Method", group = GRP_MAIN, tooltip = "Select the moving average calculation method")
int maLenInput = input.int(20, "Length", minval = 1, maxval = 500, group = GRP_MAIN, inline = "ma")
color maClrInput = input.color(color.blue, "", group = GRP_MAIN, inline = "ma")
float srcInput = input.source(close, "Source", group = GRP_MAIN)
// =============================================
// INPUTS - Bollinger Bands (conditional)
// =============================================
string GRP_BB = "Bollinger Bands"
bool showBBInput = input.bool(false, "Enable Bands", group = GRP_BB)
float bbMultInput = input.float(2.0, "Multiplier", minval = 0.5, step = 0.5, group = GRP_BB, active = showBBInput, inline = "bb")
color bbClrInput = input.color(color.gray, "", group = GRP_BB, active = showBBInput, inline = "bb")
// =============================================
// INPUTS - Signals (conditional)
// =============================================
string GRP_SIG = "Signals"
SignalDisplay sigModeInput = input.enum(SignalDisplay.off, "Display Mode", group = GRP_SIG)
bool sigActive = sigModeInput != SignalDisplay.off
color sigBullInput = input.color(color.teal, "Bull", group = GRP_SIG, active = sigActive, inline = "sigclr")
color sigBearInput = input.color(color.red, "Bear", group = GRP_SIG, active = sigActive, inline = "sigclr")
// =============================================
// CALCULATIONS
// =============================================
float ma = switch maMethodInput
MAMethod.sma => ta.sma(srcInput, maLenInput)
MAMethod.ema => ta.ema(srcInput, maLenInput)
MAMethod.wma => ta.wma(srcInput, maLenInput)
MAMethod.vwma => ta.vwma(srcInput, maLenInput)
float bbBasis = ta.stdev(srcInput, maLenInput) * bbMultInput
float bbUpper = ma + bbBasis
float bbLower = ma - bbBasis
bool bullSignal = ta.crossover(close, ma)
bool bearSignal = ta.crossunder(close, ma)
// =============================================
// PLOTTING
// =============================================
plot(ma, "Moving Average", maClrInput, 2)
plot(showBBInput ? bbUpper : na, "BB Upper", bbClrInput)
plot(showBBInput ? bbLower : na, "BB Lower", bbClrInput)
// Arrow signals
plotshape(sigModeInput == SignalDisplay.arrows and bullSignal ? low : na, "Bull Signal", shape.triangleup, location.belowbar, sigBullInput, size = size.small)
plotshape(sigModeInput == SignalDisplay.arrows and bearSignal ? high : na, "Bear Signal", shape.triangledown, location.abovebar, sigBearInput, size = size.small)
// Bar color signals
barcolor(sigModeInput == SignalDisplay.colors ? (close > ma ? sigBullInput : sigBearInput) : na)
The settings panel for this indicator has a clear visual hierarchy. The main settings group appears first with the most important choices. The Bollinger Bands section is entirely optional and only activates when the user checks the toggle. The Signals section uses an enum dropdown with three modes, and the color inputs only activate when a signal mode is selected.
To illustrate the difference these patterns make, consider the same indicator written without any of the v6 input features.
// v5 style - flat, unorganized, all always editable
string maType = input.string("SMA", "MA Type", options = ["SMA", "EMA", "WMA", "VWMA"])
int maLen = input.int(20, "MA Length")
color maColor = input.color(color.blue, "MA Color")
bool showBB = input.bool(false, "Show BB")
float bbMult = input.float(2.0, "BB Mult")
color bbColor = input.color(color.gray, "BB Color")
string sigMode = input.string("Off", "Signal Mode", options = ["Arrows", "Bar Colors", "Off"])
color bullColor = input.color(color.green, "Bull Color")
color bearColor = input.color(color.red, "Bear Color")
All nine inputs appear in a single flat list. The BB multiplier and color are always editable even when bands are hidden. The signal colors are always editable even when signals are turned off. There is no visual grouping and no indication of which settings relate to which feature.
The v6 version shown in the complete example above has the same nine inputs organized into three collapsible groups. Five of the nine inputs are conditionally active, only becoming editable when their parent feature is enabled3. Dropdown menus use type-safe enums instead of raw strings2. The result is a settings panel that feels deliberately designed rather than thrown together.
When designing input panels for your own scripts, follow these principles for the best user experience.
The combination of these patterns produces settings panels that rival commercial TradingView indicators. Your users will spend less time configuring and more time using your scripts, which translates directly into higher adoption, better reviews, and more followers on TradingView.
1 Pine Script Release Notes
2 Pine Script v5 to v6 Migration Guide
3 Pine Script v6 Language Reference
4 Pine Script User Manual