How detection works¶
Figma nodes don't carry a semantic "this is a button" flag. The tool uses a three-layer strategy.
Layer 1: Name patterns¶
Component names are matched case-insensitively against a curated list:
button, btn, cta
link, anchor
input, field, text_field, search_bar
toggle, switch
checkbox, check_box
radio
dropdown, select, picker
tab, tab_bar
card
modal, dialog, sheet
nav_bar, bottom_nav
carousel, slider
chip, tag, badge
Layer 2: Prototype interactions¶
Any node with a Figma prototype interaction (click, hover, drag, etc.) is classified as interactive regardless of its name. This catches elements that look like cards or tiles but are actually clickable.
Legacy non-empty transitionNodeID also counts as a prototype interaction, as
documented in the Figma node reference.
Decorative-name exclusions still take precedence by default, including icon-only
CTAs whose names begin with Icon.
Layer 3: Component types¶
Only these Figma node types are considered:
COMPONENT- the master definitionCOMPONENT_SET- a variant containerINSTANCE- a placed instance
Plain FRAME, GROUP, TEXT, and RECTANGLE nodes are skipped unless they have a
prototype interaction attached.
Names such as Button/Continue and Link/Terms retain their inferred type even
on raw frames or text. A node with an interaction and no type match uses
interactive. Exclusions below still take precedence.
Exclusion patterns¶
detection.overrides lets a design system include or exclude controls explicitly.
Rules run in order; the first match wins. A rule selects either a case-insensitive
name glob (match) or an exact Figma node_id. Included nodes use the rule's
type (default interactive); naming and action lookup still use the normal
configuration. Overrides take precedence over built-in name exclusions, but
cannot resurrect an excluded/hidden ancestor or bypass the hidden-layer policy.
detection:
overrides:
- node_id: "1:10"
type: button
- match: "Acme/Action*"
type: button
- match: "Acme/Decorative*"
action: exclude
action defaults to include; exclude prunes the matching subtree. A child
override is visited only if its ancestors are traversed. To target independent
controls inside detected containers, also enable traverse_interactive_children.
Unknown keys, empty selectors and invalid actions fail during config loading.
Even if a name matches layer 1, these exclusions drop it:
icon(icons inside buttons, not standalone CTAs)divider,separatorplaceholder,hintloader,spinner
Screen name derivation¶
Screens are frames directly under a page or nested inside SECTION containers.
Discovery stops at each screen frame, so its internal layout frames do not become
additional screens. Section names do not enter event names; the Figma page still
supplies the flow. This traversal does not use screen_name.max_depth, which is
reserved: only its legacy value 2 is accepted; other values fail validation.
The "screen" for each event comes from the Figma hierarchy, not the element. Given:
Page: "Home"
Frame: "Home - Default" → screen: home
Frame: "Home - With Offer" → screen: home (variant, collapses)
Frame: "Home - Skeleton" → screen: home (variant, collapses)
The - With Offer example requires adding that suffix to the configured list;
it is not a default suffix. Every variant frame is visited. Controls that generate
the same full event name within a page share one event with all their source node IDs; controls
unique to later variants create additional events. The first control supplies the
description and legacy primary source. This also applies to repeated labels in
one frame and names intentionally combined by a custom naming pattern.
Variant collapsing uses naming.screen_name.strip_suffixes. Numbered prefixes like
01 - Welcome strip to welcome when naming.screen_name.strip_prefixes: true.
JSON includes both the original source field and a sources array containing
all figma:node_id:... references. Older single-source files still load. When
upgrading, regenerate the taxonomy and review newly discovered variant controls
and source additions before accepting the new baseline. Validation and diff report
source additions/removals as drift; source ordering alone is ignored. A rename
can match through any shared node when both events have a unique correspondence.
Ambiguous splits/merges use unchanged names where possible, otherwise additions
and removals, rather than guessing renames.
Names that differ before truncation but collide at max_event_length cause an
actionable error before output is written. Increase the limit, adjust the naming
pattern, or rename the controls.
Pages and pageviews¶
The owning page supplies each event's flow/category. Page IDs distinguish pages
even when their display names match. Controls from different pages cannot share
an event name: add {page} to naming.pattern or rename the screens. {page} is
the normalized page name; if page names normalize identically, rename the pages
as well. The default pattern remains unchanged, and qualification is explicit so
filtering to one page does not rename its events.
CLI and MCP inventory every screen frame on included pages, even frames with no detected controls. Pageviews use the screen frame IDs as sources, combining all variants within that page. A frame organized inside a section is included; layout frames inside a screen are not separate screens. Page exclusion and explicit page selection apply equally to controls and pageviews. Control events retain control IDs rather than replacing them with frame IDs.
When upgrading an older baseline, expect added pageview sources and new pageviews for empty screens. Frame IDs allow unambiguous pageview renames to be recognized. Moving a screen or renaming its page also reports category changes when event names stay unchanged. Review these changes before accepting the new baseline.
Python callers can pass screens=extract_screens(figma_file, config) to
generate_taxonomy(elements, config, screens=...) to include the full inventory.
The existing two-argument call still works, deriving known screens from elements;
it cannot discover empty frames. Hand-built elements without frame IDs retain
source-less pageviews rather than inventing IDs. Missing page IDs fall back to
the page name for legacy hand-built inputs.
Text content vs component name¶
When naming.element_name.use_text_content: true (the default), the tool prefers the
element's visible text over the component name. This matters because:
With text content, you get ..._apply_now_clicked (matches the user's mental model).
Without, you'd get ..._button_primary_large_clicked (bound to the design system,
breaks when the component is restyled).
When there is no immediate child label, extraction searches visible nested layout children. It does not borrow text from independently interactive descendants. If no text exists, component-name fallback is used unless explicitly disabled; disabled fallback produces an error containing the node ID.
Hidden layers and nested controls¶
detection.include_hidden: true preserves the existing default. Set it to false
to exclude hidden nodes and their descendants, including hidden screen frames and
sections. The screen inventory and control extraction follow the same policy.
detection.traverse_interactive_children: false preserves parent suppression:
a detected card/form/button owns the interaction and its descendants are not
separate controls. Set it to true to collect nested controls as well, then review
whether these correspond to independent gestures. Every added control retains
its own source ID. Decorative exclusions still remove entire subtrees.
Instance componentProperties entries of type VARIANT are retained on extracted
elements as Name=Value strings. They do not automatically become event properties
or change AI prompts. Synthetic coverage is in tests/fixtures/detection_policy.json.
When detection goes wrong¶
Use figma-taxonomy extract --fixture design.json --explain decisions.json to
write an optional JSON report alongside the normal taxonomy, or pass explain=true
to the MCP extraction tool. With --page, the report covers the selected page.
It does not change events or enable AI.
Report schema_version: 1 contains one nodes record per supplied node, including
nodes skipped by traversal. Each record has its Figma ID/source, original name
and path, page/screen context, variants, element_type, control_detected,
reason, zero-based matched_rule (or null), and actual generated event_names.
included means the node is a source of an event, including a screen pageview.
Control exclusion does not remove a screen from the separate pageview inventory.
The top-level decisions list records event-name collisions and unnamed screens or controls. Each collision entry has
reason: "collision", node_id for the disambiguated control,
conflicting_node_id for the original control, source_labels for both original
labels (or component names in component mode or fallback), original_event_name, and the
resolved event_name. Unnamed-screen entries have reason: "unnamed_screen",
node_id, raw_frame_name, and the node-ID-derived screen_name.
Unnamed-control entries have reason: "unnamed_control", node_id, raw_label, and the node-ID-derived element_name.
The list is empty when neither disambiguation nor a screen/control name fallback was needed.
Reasons identify name or prototype detection, explicit overrides, hidden layers,
excluded pages/names, suppressed descendants (*_ancestor), noninteractive nodes,
pageview-only screens, or nodes outside screens. A matched rule refers to the
ordered detection.overrides list. Reports include design text and should be
shared with the same care as the source fixture. Choose a report path distinct
from fixture/config inputs and generated taxonomy files.
If an event you expected isn't showing up:
- Check the component name in Figma against the pattern list
- Check for an exclusion (
iconis a common false-negative trigger) - Check that the element is a
COMPONENT/INSTANCE, not a rawFRAME - Add a prototype interaction to include a raw node that is not excluded
- Or override by renaming the component to include a keyword like
button
The report connects detection decisions to the final events, including merged sources, so inspect it before changing a naming rule or accepting new controls.