Skip to main content
Participant
August 27, 2026
Question

Add-on or feature to hide / unhide layers based on selection / check box?

  • August 27, 2026
  • 5 replies
  • 58 views

Hello,

Is it possible for layers containing artwork / text to be hidden / unhidden, based on a selection or check box that is made as part of a decision process?

For example. Like when a new document is created, there is a selection process for the size and details of that document the user has to input. I would like a similar selection process; however, it would have questions like “is the product Round or Square”. Depending on the check box / pick list value selected, the artwork on the appropriate layer would be hidden / unhidden in the correct location.

To give a bit more context, I am a Technical Artworker for a company needing to create labels for products. I currently have various enormous templates where I need to drag and drop labels of pre-determined size and change the text, depending on the product specification. This is long winded and open to human error. A selection process, tool, or add-on that helped with this would be a huge time saving.

Any feedback would be most welcomed. 

Thank you,

David

5 replies

m1b
Community Expert
Community Expert
September 3, 2026

Hi ​@Vibrant_idea8f58 This is a good idea for a new script!

I've written it: "Layer Setup.js" asks pre-configured questions in a dialog, then sets layer visibility
and layer lock states based on the answers.

 

FILES

    Layer Setup.js            the script you run
    Layer Setup.json        your questions; must sit beside the script*

* If Layer Setup.json isn't found beside the script, you're asked to pick a file. SECURITY WARNING: do not choose a file that you haven’t written yourself.

 

USAGE

    1. Open the example document (attached).
    2. Run Layer Setup.js.
    3. Answer the questions and click "Set Layers".

The dialog opens showing how the document currently stands. The answers are
populated by reading the current layer status.

Layers named in the JSON but missing from the document are listed in an
alert afterwards; everything else is still applied.

 

CONFIG JSON

This is the file you create for your exact requirements. Keep it in the same folder as the script.

{
    "title": "Layer Setup",
    "questions": [
        {
            "label": "Pack size:",
            "type": "radio",
            "answers": [
                { "label": "Small",  "show": ["Size Small"] },
                { "label": "Large",  "show": ["Size Large"] }
            ]
        },
        {
            "label": "Show extras:",
            "type": "checkbox",
            "answers": [
                { "label": "Barcode", "show": ["Barcode"] },
                { "label": "Notes",   "show": ["Notes"], "lock": ["Notes"] }
            ]
        }
    ]
}

    title    the dialog's title (optional)
    type     "radio" to pick one, or "checkbox" to pick any

Each answer needs a "label", plus any of these, each an array of layer names: show, hide, unlock, lock;

 

NOTES

- Layer names must match exactly. Sublayers are searched too; if two layers share a name, the first one found wins.

- If two questions name the same layer, the later question wins, so it is best to keep each layer under one question.

- Within a question, a layer defaults to the opposite of what its answer asserts. So "show" means "hidden unless chosen", and "unlock" means "locked unless chosen". You never write the opposite list yourself: in the example above, choosing Small hides Size Large automatically.

This is also what lets an answer with no keys work. Give a radio question a plain { "label": "None" } answer, and choosing it clears everything the other answers in that question would have shown.
 

Here is the script. Give it a go and let me know how it goes. I plan to use it myself!

– Mark

 

THE SCRIPT

/**
* @file Layer Setup.js
*
* Asks the user pre-configured questions which
* determine the layer visibility/lock status.
*
* The questions live in a JSON file beside this script (see
* `CONFIG_FILE_NAME`) so each job can have its own set of
* questions without editing the script. Every answer lists the
* layers it shows, hides, unlocks or locks:
*
* {
* "title": "Layer Setup",
* "questions": [
* {
* "label": "Pack size:",
* "type": "radio",
* "answers": [
* { "label": "Small", "show": ["Size Small"] },
* { "label": "Large", "show": ["Size Large"] }
* ]
* }
* ]
* }
*
* Within a question, every layer an answer can affect starts at
* the *opposite* of what that answer asserts, so "show" implies
* "hidden unless chosen" and "unlock" implies "locked unless
* chosen" — the complement never has to be spelled out.
*
* The dialog opens with the answers reverse-determined from the
* document's current layer states, so nothing is stored in the
* document and the dialog always reflects reality.
*
* Note: if two questions govern the same layer, the later
* question wins.
*
* Demo files: "Make Test Document.js" builds a document that
* matches "Layer Setup.json".
*
* @author m1b
* @version 2026-09-02
* @discussion https://community.adobe.com/questions-652/add-on-or-feature-to-hide-unhide-layers-based-on-selection-check-box-1639036?tid=1639036&fid=652
*/

/**
* How each answer effect maps to a layer state.
* `on` is asserted when the answer is chosen and
* `off` is the question's default for that layer.
*/
var ANSWER_EFFECTS = [
{ key: 'show', property: 'visible', on: true, off: false },
{ key: 'hide', property: 'visible', on: false, off: true },
{ key: 'unlock', property: 'locked', on: false, off: true },
{ key: 'lock', property: 'locked', on: true, off: false },
];

(function () {

var CONFIG_FILE_NAME = 'Layer Setup.json';

if (0 === app.documents.length)
return alert('Layer Setup\nPlease open a document and try again.');

var doc = app.activeDocument;

try {

var configFile = getConfigFile(CONFIG_FILE_NAME);

if (undefined === configFile)
// user cancelled the file dialog
return;

var config = parseJSONFile(configFile);
validateConfig(config);

var layersByName = getLayersByName(doc);
var answers = getAnswersFromLayers(config, layersByName);

answers = showUI(config, answers);

if (undefined === answers)
// user cancelled
return;

var missing = applyLayerStates(resolveLayerStates(config, answers), layersByName);
keepActiveLayerUsable(doc);

if (missing.length > 0)
alert('Layer Setup\nThese layers were named in "' + configFile.name + '" but are not in the document:\n\n' + missing.join('\n'));

} catch (error) {

alert('Layer Setup\n' + error.message);

}

})();

/* ----------------- *
* USER INTERFACE *
* ----------------- */

/**
* Shows the questions, one per line, and returns the answers.
* Answers wrap onto extra lines rather than stacking vertically.
* @author m1b
* @version 2026-09-02
* @param {Object} config - the parsed configuration.
* @param {Array<Array<Boolean>>} answers - the initial answers, per question.
* @returns {Array<Array<Boolean>>?} - the chosen answers, or undefined if cancelled.
*/
function showUI(config, answers) {

var LABEL_WIDTH = 120;
var ANSWERS_WIDTH = 380;
var CONTROL_PADDING = 30;
var CONTROL_SPACING = 10;

var w = new Window('dialog { text: "Layer Setup", properties: { resizeable: false }, orientation: "column", alignChildren: ["fill", "top"], margins: 16, spacing: 12 }');
var questionsGroup = w.add('Group { orientation: "column", alignChildren: ["fill", "top"], spacing: 10 }');

w.text = config.title || 'Layer Setup';

var controls = [];

for (var i = 0; i < config.questions.length; i++) {

var question = config.questions[i];
var isRadio = ('radio' === question.type);

var questionRow = questionsGroup.add('Group { orientation:"row", alignChildren:["left","top"], spacing:8 }');

var label = questionRow.add('StaticText { justify:"right" }');
label.text = question.label;
label.preferredSize = [LABEL_WIDTH, -1];

var answersColumn = questionRow.add('Group { orientation:"column", alignChildren:["left","top"], spacing:2 }');
var answersRow = answersColumn.add('Group { orientation:"row", alignChildren:["left","center"], spacing:' + CONTROL_SPACING + ' }');
var rowWidth = 0;

var questionControls = [];

for (var j = 0; j < question.answers.length; j++) {

var controlWidth = measureTextWidth(w.graphics, question.answers[j].label) + CONTROL_PADDING;

// wrap to a new line rather than growing the dialog
if (
rowWidth > 0
&& rowWidth + controlWidth > ANSWERS_WIDTH
) {

answersRow = answersColumn.add('Group { orientation:"row", alignChildren:["left","center"], spacing:' + CONTROL_SPACING + ' }');
rowWidth = 0;

}

var control = isRadio
? answersRow.add('RadioButton { alignment:["left","center"] }')
: answersRow.add('Checkbox { alignment:["left","center"] }');

control.text = question.answers[j].label;
control.preferredSize = [controlWidth, -1];
control.value = (true === answers[i][j]);

questionControls.push(control);
rowWidth += controlWidth + CONTROL_SPACING;

}

// ScriptUI only auto-groups radio buttons that share an
// immediate parent, so wrapped rows need manual exclusivity
if (isRadio)
for (var j = 0; j < questionControls.length; j++)
questionControls[j].onClick = makeRadioClickHandler(questionControls, j);

controls.push(questionControls);

}

var buttonsGroup = w.add('Group { orientation: "row", alignment: ["right", "bottom"] }');
var cancelButton = buttonsGroup.add('Button { text: "Cancel", properties: { name: "cancel" } }');
var setButton = buttonsGroup.add('Button { text: "Set Layers", properties: { name: "ok" } }');

w.defaultElement = setButton;
w.cancelElement = cancelButton;

if (1 !== w.show())
return;

var chosen = [];

for (var i = 0; i < controls.length; i++) {

var questionAnswers = [];

for (var j = 0; j < controls[i].length; j++)
questionAnswers.push(true === controls[i][j].value);

chosen.push(questionAnswers);

}

return chosen;

};

/**
* Returns a click handler that keeps a wrapped
* group of radio buttons mutually exclusive.
* @author m1b
* @version 2026-09-02
* @param {Array<RadioButton>} group - all the radio buttons of one question.
* @param {Number} index - the index of the clicked button.
* @returns {Function}
*/
function makeRadioClickHandler(group, index) {

return function onRadioClick() {

for (var i = 0; i < group.length; i++)
group[i].value = (i === index);

};

};

/**
* Returns the width of `text` as drawn, with a
* rough fallback where measuring isn't available.
* @author m1b
* @version 2026-09-02
* @param {ScriptUIGraphics} gfx - graphics of a ScriptUI element.
* @param {String} text - the text to measure.
* @returns {Number} - the width in pixels.
*/
function measureTextWidth(gfx, text) {

var width = 0;

try {
width = gfx.measureString(text, gfx.font).width;
} catch (error) {
// measureString isn't implemented in every host
}

if (!width)
width = text.length * 7;

return width;

};

/* ------------------------ *
* ANSWERS <-> LAYER STATE *
* ------------------------ */

/**
* Returns the layer states implied by the given answers.
* @author m1b
* @version 2026-09-02
* @param {Object} config - the parsed configuration.
* @param {Array<Array<Boolean>>} answers - the chosen answers, per question.
* @returns {Object} - map of layer name to { visible, locked }.
*/
function resolveLayerStates(config, answers) {

var states = {};

for (var i = 0; i < config.questions.length; i++)
mergeStates(states, buildQuestionState(config.questions[i], answers[i]));

return states;

};

/**
* Returns the layer states implied by one question's answers.
* @author m1b
* @version 2026-09-02
* @param {Object} question - a question from the configuration.
* @param {Array<Boolean>} selected - which of the question's answers are chosen.
* @returns {Object} - map of layer name to { visible, locked }.
*/
function buildQuestionState(question, selected) {

var states = {};

// every layer this question can affect starts at
// the opposite of what its answer asserts
for (var i = 0; i < question.answers.length; i++)
for (var e = 0; e < ANSWER_EFFECTS.length; e++)
setStates(states, question.answers[i][ANSWER_EFFECTS[e].key], ANSWER_EFFECTS[e].property, ANSWER_EFFECTS[e].off);

// then the chosen answers assert their effects
for (var i = 0; i < question.answers.length; i++) {

if (!selected[i])
continue;

for (var e = 0; e < ANSWER_EFFECTS.length; e++)
setStates(states, question.answers[i][ANSWER_EFFECTS[e].key], ANSWER_EFFECTS[e].property, ANSWER_EFFECTS[e].on);

}

return states;

};

/**
* Returns just the layer states that one answer asserts
* when it is chosen, ignoring the question's defaults.
* @author m1b
* @version 2026-09-02
* @param {Object} answer - an answer from the configuration.
* @returns {Object} - map of layer name to { visible, locked }.
*/
function buildAnswerState(answer) {

var states = {};

for (var e = 0; e < ANSWER_EFFECTS.length; e++)
setStates(states, answer[ANSWER_EFFECTS[e].key], ANSWER_EFFECTS[e].property, ANSWER_EFFECTS[e].on);

return states;

};

/**
* Reverse-determines the answers from the document's
* current layer states, so the dialog opens showing
* how the document actually stands.
* @author m1b
* @version 2026-09-02
* @param {Object} config - the parsed configuration.
* @param {Object} layersByName - map of layer name to Layer.
* @returns {Array<Array<Boolean>>} - the answers, per question.
*/
function getAnswersFromLayers(config, layersByName) {

var answers = [];

for (var i = 0; i < config.questions.length; i++) {

var question = config.questions[i];

if ('checkbox' === question.type) {

// each checkbox is independent: it is ticked when the
// document already matches everything the answer asserts
var selected = [];

for (var j = 0; j < question.answers.length; j++)
selected.push(1 === scoreState(buildAnswerState(question.answers[j]), layersByName));

answers.push(selected);
continue;

}

// choose the radio answer whose resulting layer
// states best match the document as it stands
var bestIndex = 0;
var bestScore = -1;

for (var j = 0; j < question.answers.length; j++) {

var score = scoreState(buildQuestionState(question, makeSelection(question.answers.length, j)), layersByName);

if (score > bestScore) {
bestScore = score;
bestIndex = j;
}

}

answers.push(makeSelection(question.answers.length, bestIndex));

}

return answers;

};

/**
* Returns the proportion of `states` the document already matches.
* Layers named but not present are ignored.
* @author m1b
* @version 2026-09-02
* @param {Object} states - map of layer name to { visible, locked }.
* @param {Object} layersByName - map of layer name to Layer.
* @returns {Number} - 0..1, or 0 when nothing is asserted.
*/
function scoreState(states, layersByName) {

var matched = 0;
var total = 0;

for (var name in states) {

if (
!states.hasOwnProperty(name)
|| !layersByName.hasOwnProperty(name)
)
continue;

var layer = layersByName[name];
var state = states[name];

if (undefined !== state.visible) {

total++;

if (layer.visible === state.visible)
matched++;

}

if (undefined !== state.locked) {

total++;

if (layer.locked === state.locked)
matched++;

}

}

if (0 === total)
return 0;

return matched / total;

};

/**
* Sets one state property for each of the named layers.
* @author m1b
* @version 2026-09-02
* @param {Object} states - map of layer name to { visible, locked }.
* @param {Array<String>} [names] - the layer names to set.
* @param {String} property - 'visible' or 'locked'.
* @param {Boolean} value - the value to set.
*/
function setStates(states, names, property, value) {

if (undefined === names)
return;

for (var i = 0; i < names.length; i++) {

if (!states.hasOwnProperty(names[i]))
states[names[i]] = {};

states[names[i]][property] = value;

}

};

/**
* Merges `source` states into `target`, property by property.
* @author m1b
* @version 2026-09-02
* @param {Object} target - the states to merge into.
* @param {Object} source - the states to merge from.
*/
function mergeStates(target, source) {

for (var name in source) {

if (!source.hasOwnProperty(name))
continue;

if (!target.hasOwnProperty(name))
target[name] = {};

if (undefined !== source[name].visible)
target[name].visible = source[name].visible;

if (undefined !== source[name].locked)
target[name].locked = source[name].locked;

}

};

/**
* Returns an array of booleans with only `index` true.
* @author m1b
* @version 2026-09-02
* @param {Number} length - the length of the array.
* @param {Number} index - the index to set true.
* @returns {Array<Boolean>}
*/
function makeSelection(length, index) {

var selection = [];

for (var i = 0; i < length; i++)
selection.push(i === index);

return selection;

};

/* ------------------ *
* DOCUMENT LAYERS *
* ------------------ */

/**
* Returns a map of every layer and sublayer, by name.
* Where names are duplicated, the first found wins.
* @author m1b
* @version 2026-09-02
* @param {Document} doc - an Illustrator document.
* @returns {Object} - map of layer name to Layer.
*/
function getLayersByName(doc) {

var found = {};

/**
* Collects layers recursively.
* @param {Layers} layers - a layers collection.
*/
function collect(layers) {

for (var i = 0; i < layers.length; i++) {

if (!found.hasOwnProperty(layers[i].name))
found[layers[i].name] = layers[i];

if (layers[i].layers.length > 0)
collect(layers[i].layers);

}

};

collect(doc.layers);

return found;

};

/**
* Applies the states to the document's layers.
* @author m1b
* @version 2026-09-02
* @param {Object} states - map of layer name to { visible, locked }.
* @param {Object} layersByName - map of layer name to Layer.
* @returns {Array<String>} - the names of any layers not found.
*/
function applyLayerStates(states, layersByName) {

var missing = [];

for (var name in states) {

if (!states.hasOwnProperty(name))
continue;

if (!layersByName.hasOwnProperty(name)) {
missing.push(name);
continue;
}

var layer = layersByName[name];
var state = states[name];
var wasLocked = layer.locked;

// unlock first so that visibility can always be set
if (wasLocked)
layer.locked = false;

if (undefined !== state.visible)
layer.visible = state.visible;

layer.locked = (undefined === state.locked)
? wasLocked
: state.locked;

}

return missing;

};

/**
* Makes sure the active layer is one the user can draw on,
* because Illustrator misbehaves when it is hidden or locked.
* @author m1b
* @version 2026-09-02
* @param {Document} doc - an Illustrator document.
*/
function keepActiveLayerUsable(doc) {

try {

if (
doc.activeLayer.visible
&& !doc.activeLayer.locked
)
return;

} catch (error) {
// reading activeLayer can fail when every layer is hidden
}

for (var i = 0; i < doc.layers.length; i++) {

if (
doc.layers[i].visible
&& !doc.layers[i].locked
) {

doc.activeLayer = doc.layers[i];
return;

}

}

};

/* ------------------ *
* CONFIGURATION *
* ------------------ */

/**
* Returns the configuration file, looking beside this
* script first and then asking the user.
* @author m1b
* @version 2026-09-02
* @param {String} fileName - the expected file name.
* @returns {File?} - the configuration file, or undefined if cancelled.
*/
function getConfigFile(fileName) {

var file = File(File($.fileName).parent.fsName + '/' + fileName);

if (file.exists)
return file;

var chosen = File.openDialog('Choose the Layer Setup configuration file', undefined, false);

if (null === chosen)
return;

return chosen;

};

/**
* Reads and parses a JSON file.
* @author m1b
* @version 2026-09-02
* @param {File} file - a JSON file.
* @returns {Object} - the parsed configuration.
*/
function parseJSONFile(file) {

file.encoding = 'UTF-8';

if (!file.open('r'))
throw new Error('parseJSONFile: could not open "' + file.name + '".');

var text = file.read();
file.close();

var data;

// ExtendScript has no JSON parser, so the text is
// evaluated — only use configuration files you trust
try {
data = eval('(' + text + ')');
} catch (error) {
throw new Error('parseJSONFile: bad JSON in "' + file.name + '".\n' + error.message);
}

return data;

};

/**
* Throws if the configuration isn't usable.
* @author m1b
* @version 2026-09-02
* @param {Object} config - the parsed configuration.
*/
function validateConfig(config) {

if (
null === config
|| 'object' !== typeof config
|| !(config.questions instanceof Array)
|| 0 === config.questions.length
)
throw new Error('validateConfig: configuration needs a `questions` array.');

for (var i = 0; i < config.questions.length; i++) {

var question = config.questions[i];

if (
null === question
|| 'object' !== typeof question
|| 'string' !== typeof question.label
)
throw new Error('validateConfig: question ' + (i + 1) + ' needs a `label`.');

if (
'radio' !== question.type
&& 'checkbox' !== question.type
)
throw new Error('validateConfig: question "' + question.label + '" needs a `type` of "radio" or "checkbox".');

if (
!(question.answers instanceof Array)
|| 0 === question.answers.length
)
throw new Error('validateConfig: question "' + question.label + '" needs an `answers` array.');

for (var j = 0; j < question.answers.length; j++)
if (
null === question.answers[j]
|| 'object' !== typeof question.answers[j]
|| 'string' !== typeof question.answers[j].label
)
throw new Error('validateConfig: answer ' + (j + 1) + ' of question "' + question.label + '" needs a `label`.');

}

};

 

Monika Gause
Community Expert
Community Expert
August 27, 2026

Detecting whether something is round or square would require either a script (which checks for the type of point) or artificial intelligence. Both approaches can likely fail. The script, because someone set up weird anchor points and AI, because it’s AI.

So probably you might want to investigate whether you can put more intelligence into setting up the initial templates by naming and structuring things appropriately.

In ordet to text the ability of artificial intelligence, you can try out the AI assistant in the Illustrator Beta version.

Participant
August 28, 2026

Hello, thanks for your feedback; however I wasn’t implying that AI would be needed to recognise shapes of artwork. I was saying that a selection process would occur at the beginning of the document creation that would simply hide / unhide layers based on the artwork content.

Harshika Verma
Community Manager
Community Manager
August 27, 2026

Hi Vibrant_idea8f58,


Thank you for reaching out and sharing your idea. Could you please submit your idea on the UserVoice page? This will assist the product team in monitoring your request, and also share the link to your idea with us here for tracking purposes, so that others can upvote it as well.


In the meantime, we will keep the discussion open for our community experts to offer any suggestions regarding the workflow.


Thanks,

Harshika

Dave Creamer of IDEAS
Community Expert
Community Expert
August 27, 2026

Like Layer Groups in Photoshop. 

You can check out these plugins:

(I didn’t delve deep into them, so not sure if they will do what you want or if they are up to date.)

https://www.hotdoor.com/controlplugins  Particulayer plugin

https://www.axaio.com/doku.php/en:products:madeforlayers

David Creamer: Community Expert (ACI and ACE 1995-2023)