Skip to main content
NVDA Objects are the fundamental building blocks that represent controls and GUI elements in NVDA. Every widget, regardless of its underlying API, is represented as an NVDAObject instance.

What is an NVDA Object?

An NVDA Object provides:
  • Standardized properties: name, role, value, states, description
  • Navigation: parent, children, next, previous relationships
  • Event handling: Response to focus, value changes, etc.
  • Scripting: Custom commands and gesture bindings
  • Text access: Through TextInfo objects
import api

# Get the current focus object
focusObj = api.getFocusObject()

# Access standard properties
name = focusObj.name
role = focusObj.role
value = focusObj.value
states = focusObj.states
description = focusObj.description

# Get location on screen
location = focusObj.location  # (left, top, width, height)

Base Class: NVDAObject

The NVDAObject class is defined in source/NVDAObjects/__init__.py:
NVDAObject base class
class NVDAObject(
    documentBase.TextContainerObject,
    baseObject.ScriptableObject,
    metaclass=DynamicNVDAObjectType,
):
    """NVDA's representation of a single control/widget.
    
    Every widget, regardless of how it is exposed by an application or 
    the operating system, is represented as a single NVDAObject instance.
    """
    
    cachePropertiesByDefault = True
    TextInfo = NVDAObjectTextInfo
    
    # Core properties implemented via auto-property pattern
    def _get_name(self) -> str:
        """The label or name of this object"""
        return ""
    
    def _get_role(self) -> controlTypes.Role:
        """The role or type of control"""
        return controlTypes.Role.UNKNOWN
    
    def _get_value(self) -> str:
        """The value of the control"""
        return ""
    
    def _get_states(self) -> Set[controlTypes.State]:
        """Set of state constants"""
        return set()

API Classes

NVDA supports multiple accessibility APIs through specialized classes:

IAccessible Objects

For MSAA/IAccessible controls:
IAccessible Example
from NVDAObjects.IAccessible import IAccessible

class CustomButton(IAccessible):
    def _get_name(self):
        # Override name from IAccessible
        return self.IAccessibleObject.accName(self.IAccessibleChildID) or "Unnamed"
    
    def _get_role(self):
        return controlTypes.Role.BUTTON

UI Automation Objects

For modern Windows UIA controls:
UIA Example
from NVDAObjects.UIA import UIA

class CustomEdit(UIA):
    def _get_value(self):
        # Access UIA properties
        return self.UIAElement.CurrentValue
    
    def _get_states(self):
        states = super()._get_states()
        if self.UIAElement.CurrentIsReadOnly:
            states.add(controlTypes.State.READONLY)
        return states

Window Objects

For direct Win32 window access:
Window Example
from NVDAObjects.window import Window

class CustomWindow(Window):
    def _get_name(self):
        # Access window properties
        return self.windowText or "Untitled"
    
    @property
    def windowClassName(self):
        return self._windowClassName
    
    @property
    def windowControlID(self):
        return self._windowControlID

Dynamic Class Creation

One of NVDAโ€™s most powerful features is dynamic class creation. When an object is instantiated:
Dynamic class creation flow
class DynamicNVDAObjectType(baseObject.ScriptableObject.__class__):
    _dynamicClassCache = {}
    
    def __call__(self, chooseBestAPI=True, **kwargs):
        # 1. Find the best API class
        if chooseBestAPI:
            APIClass = self.findBestAPIClass(kwargs)
            if not APIClass:
                return None
        
        # 2. Instantiate the API class
        obj = APIClass.__new__(APIClass, **kwargs)
        obj.__init__(**kwargs)
        
        # 3. Build overlay class list
        clsList = []
        obj.findOverlayClasses(clsList)
        
        # 4. Let app module choose classes
        if obj.appModule:
            obj.appModule.chooseNVDAObjectOverlayClasses(obj, clsList)
        
        # 5. Let global plugins choose classes
        for plugin in globalPluginHandler.runningPlugins:
            plugin.chooseNVDAObjectOverlayClasses(obj, clsList)
        
        # 6. Construct dynamic class
        bases = tuple(optimized_bases)
        name = "Dynamic_" + "_".join([x.__name__ for x in clsList])
        newCls = type(name, bases, {"__module__": __name__})
        
        # 7. Mutate object to new class
        obj.__class__ = newCls
        
        # 8. Initialize overlay classes
        for cls in reversed(newCls.__mro__):
            if hasattr(cls, 'initOverlayClass'):
                cls.initOverlayClass(obj)
        
        return obj
This allows NVDA to combine generic API support with application-specific behaviors without modifying core code.

Creating Custom NVDA Objects

Step 1: Define the Class

Inherit from the appropriate base class:
Custom NVDA Object
from NVDAObjects.IAccessible import IAccessible
from scriptHandler import script
import ui
import controlTypes

class EnhancedEditField(IAccessible):
    """Custom edit field with additional functionality"""
    
    def _get_name(self):
        # Customize the name
        return f"Enhanced: {super().name}"
    
    def _get_description(self):
        # Add helpful description
        return f"Length: {len(self.value)} characters"
    
    @script(gesture="kb:NVDA+l")
    def script_reportLength(self, gesture):
        """Report the length of text in this field"""
        length = len(self.value) if self.value else 0
        ui.message(f"{length} characters")
    
    def initOverlayClass(self):
        """Called when this overlay is applied to an object"""
        # Perform initialization
        pass

Step 2: Choose When to Use It

In your app module or global plugin:
Choosing overlay classes
import appModuleHandler
from NVDAObjects.IAccessible import IAccessible
import controlTypes

class AppModule(appModuleHandler.AppModule):
    
    def chooseNVDAObjectOverlayClasses(self, obj, clsList):
        """Choose custom classes for objects in this app"""
        # Check if this is an edit field we want to enhance
        if (
            isinstance(obj, IAccessible) and
            obj.role == controlTypes.Role.EDITABLETEXT and
            obj.windowClassName == "Edit"
        ):
            # Insert our custom class at the beginning
            clsList.insert(0, EnhancedEditField)
Insert custom classes at index 0 so they are resolved first in the method resolution order (MRO).

Common Properties

Essential Properties

Essential properties
class MyObject(NVDAObject):
    def _get_name(self) -> str:
        """Label of the control (e.g., button text)"""
        return "OK"
    
    def _get_role(self) -> controlTypes.Role:
        """Type of control"""
        return controlTypes.Role.BUTTON
    
    def _get_value(self) -> str:
        """Current value (e.g., slider position, checkbox state)"""
        return "50%"
    
    def _get_states(self) -> Set[controlTypes.State]:
        """Set of states"""
        states = set()
        states.add(controlTypes.State.FOCUSABLE)
        states.add(controlTypes.State.FOCUSED)
        return states
    
    def _get_description(self) -> str:
        """Description (usually from tooltip)"""
        return "Click to confirm"

Location and Visibility

Location properties
    def _get_location(self) -> Tuple[int, int, int, int]:
        """Screen coordinates (left, top, width, height)"""
        return (100, 100, 200, 50)
    
    def _get_hasIrrelevantLocation(self) -> bool:
        """True if object is offscreen or invisible"""
        return False
    
    def _get_presentationType(self):
        """How this object should be presented"""
        return self.presType_content  # or presType_layout
Navigation properties
    def _get_parent(self) -> Optional['NVDAObject']:
        """Parent object in hierarchy"""
        return self._parent
    
    def _get_firstChild(self) -> Optional['NVDAObject']:
        """First child object"""
        return self._firstChild
    
    def _get_lastChild(self) -> Optional['NVDAObject']:
        """Last child object"""
        return self._lastChild
    
    def _get_next(self) -> Optional['NVDAObject']:
        """Next sibling"""
        return self._next
    
    def _get_previous(self) -> Optional['NVDAObject']:
        """Previous sibling"""
        return self._previous
    
    def _get_children(self) -> List['NVDAObject']:
        """List of all direct children"""
        return []

Working with States

States indicate the current condition of a control:
Using states
import controlTypes

def processObject(obj):
    states = obj.states
    
    # Check for specific states
    if controlTypes.State.FOCUSED in states:
        print("Object has focus")
    
    if controlTypes.State.CHECKED in states:
        print("Checkbox is checked")
    
    if controlTypes.State.EXPANDED in states:
        print("Tree item is expanded")
    
    # Common states
    # FOCUSABLE, FOCUSED, SELECTED, SELECTABLE
    # CHECKED, CHECKABLE, PRESSED, EXPANDED, COLLAPSED
    # READONLY, DISABLED, INVISIBLE, OFFSCREEN
    # BUSY, LINKED, VISITED, PROTECTED

Text Access

NVDA Objects can provide text access:
Text access
# Get a TextInfo object
textInfo = obj.makeTextInfo(textInfos.POSITION_FIRST)

# Move and read text
textInfo.expand(textInfos.UNIT_LINE)
line_text = textInfo.text

# Get all text
storyInfo = obj.makeTextInfo(textInfos.POSITION_ALL)
all_text = storyInfo.text

Custom TextInfo

Custom TextInfo
from textInfos import TextInfo

class CustomTextInfo(TextInfo):
    def _getStoryText(self):
        """Return all text"""
        return self.obj.value or ""
    
    def _getStoryLength(self):
        """Return text length"""
        return len(self._getStoryText())

class MyObject(NVDAObject):
    TextInfo = CustomTextInfo

Focus Redirect

Redirect focus to a different object:
Focus redirect
class ContainerObject(NVDAObject):
    def _get_focusRedirect(self):
        """When this object receives focus, redirect to its first child"""
        return self.firstChild

Tree Interceptors

Complex documents can have tree interceptors (like browse mode):
Tree interceptor
from browseMode import BrowseModeTreeInterceptor

class MyDocument(NVDAObject):
    def _get_treeInterceptorClass(self):
        """Specify tree interceptor class"""
        return BrowseModeTreeInterceptor
    
    shouldCreateTreeInterceptor = True

Practical Example: Custom Button

Complete example of a custom button with enhanced features:
Complete custom button example
from NVDAObjects.IAccessible import IAccessible
from scriptHandler import script
import ui
import tones
import controlTypes

class EnhancedButton(IAccessible):
    """A button with sound effects and additional info"""
    
    def _get_name(self):
        # Add emoji to name for fun
        base_name = super().name
        return f"๐Ÿ”˜ {base_name}"
    
    def _get_description(self):
        # Provide helpful description
        return f"Press {self.name} button to activate"
    
    def event_gainFocus(self):
        """Play sound when button receives focus"""
        tones.beep(440, 50)
        super().event_gainFocus()
    
    @script(
        gesture="kb:NVDA+b",
        description="Report button information"
    )
    def script_reportInfo(self, gesture):
        """Report detailed button information"""
        info = [
            f"Name: {self.name}",
            f"Role: {self.role}",
            f"Location: {self.location}",
            f"Window class: {self.windowClassName}",
        ]
        ui.message(", ".join(info))
    
    def initOverlayClass(self):
        """Initialize the button"""
        # Log when this overlay is applied
        log.debug(f"Enhanced button overlay applied to {self.name}")

# In app module:
class AppModule(appModuleHandler.AppModule):
    def chooseNVDAObjectOverlayClasses(self, obj, clsList):
        if obj.role == controlTypes.Role.BUTTON:
            clsList.insert(0, EnhancedButton)

Best Practices

Properties are cached by default for performance. To disable caching for a specific property:
class MyObject(NVDAObject):
    _cache_dynamicProperty = False
    
    def _get_dynamicProperty(self):
        # This will be fetched every time
        return self._getCurrentValue()
Classes in clsList should be ordered from most specific to most general:
def chooseNVDAObjectOverlayClasses(self, obj, clsList):
    # Most specific first
    clsList.insert(0, VerySpecificClass)
    # More general after
    clsList.append(GeneralClass)
NVDA uses weak references for object relationships. Donโ€™t store strong references unnecessarily:
# Good - uses weak reference
self._parentRef = weakref.ref(parent)

# Bad - strong reference creates memory leak
self._parent = parent
Only access API-specific properties when you know the object type:
from NVDAObjects.IAccessible import IAccessible

if isinstance(obj, IAccessible):
    accName = obj.IAccessibleObject.accName(obj.IAccessibleChildID)

See Also

Events

Learn about event handling

Scripts & Gestures

Add commands to objects

Writing Plugins

Create app modules and overlays

Architecture Overview

Understand the big picture

Build docs developers (and LLMs) love