> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/nvaccess/nvda/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom NVDA Objects

> Create overlay classes to customize control behavior

## Overview

NVDA Objects represent controls and widgets in applications. By creating custom NVDA Object classes (overlay classes), you can:

* Enhance accessibility of poorly accessible controls
* Provide better names, descriptions, and role information
* Customize how text content is accessed
* Add custom scripts and event handlers to specific controls
* Fix broken accessibility implementations

<Note>
  Custom NVDA Objects use the **overlay class pattern** - you create classes that add functionality without modifying the base implementation.
</Note>

## NVDA Object Hierarchy

Every widget is represented by an NVDA Object that provides:

<ParamField path="name" type="str">
  The label/name of the control (e.g., "OK", "File name")
</ParamField>

<ParamField path="role" type="Role">
  The type of control (from `controlTypes.Role`: `BUTTON`, `EDITABLETEXT`, `CHECKBOX`, etc.)
</ParamField>

<ParamField path="value" type="str">
  The current value (e.g., percentage of scrollbar, content of edit field)
</ParamField>

<ParamField path="states" type="set[State]">
  Current states (from `controlTypes.State`: `FOCUSED`, `SELECTED`, `CHECKED`, etc.)
</ParamField>

<ParamField path="description" type="str">
  Additional descriptive information (usually from tooltip)
</ParamField>

<ParamField path="location" type="tuple">
  Screen coordinates: `(left, top, width, height)`
</ParamField>

### Navigation Properties

```python navigation.py theme={null}
# Hierarchy navigation
obj.parent          # Parent object
obj.firstChild      # First child object
obj.lastChild       # Last child object
obj.children        # List of all children
obj.next            # Next sibling
obj.previous        # Previous sibling

# Simplified navigation (filters out unimportant objects)
obj.simpleParent
obj.simpleFirstChild
obj.simpleLastChild
obj.simpleNext
obj.simplePrevious
```

## Creating Overlay Classes

Overlay classes are chosen through the `chooseNVDAObjectOverlayClasses` method in app modules or global plugins:

<CodeGroup>
  ```python In App Module theme={null}
  import appModuleHandler
  from NVDAObjects.IAccessible import IAccessible
  import controlTypes

  class AppModule(appModuleHandler.AppModule):

  	def chooseNVDAObjectOverlayClasses(self, obj, clsList):
  		"""Choose overlay classes for objects in this application."""
  		if obj.windowClassName == "MyCustomButton":
  			clsList.insert(0, CustomButton)
  		elif obj.role == controlTypes.Role.LIST:
  			clsList.insert(0, EnhancedList)

  class CustomButton(IAccessible):
  	"""Custom button with enhanced functionality."""
  	
  	def _get_name(self):
  		"""Provide better name."""
  		# Custom logic to extract name
  		name = super().name
  		return f"Button: {name}"

  class EnhancedList(IAccessible):
  	"""Enhanced list with additional information."""
  	
  	def event_gainFocus(self):
  		"""Custom focus announcement."""
  		super().event_gainFocus()
  		# Additional information
  		import ui
  		ui.message(f"List with {self.childCount} items")
  ```

  ```python In Global Plugin theme={null}
  import globalPluginHandler
  from NVDAObjects.window import Window
  import controlTypes

  class GlobalPlugin(globalPluginHandler.GlobalPlugin):

  	def chooseNVDAObjectOverlayClasses(self, obj, clsList):
  		"""Choose overlay classes for any object."""
  		# Add overlay to all edit fields
  		if obj.role == controlTypes.Role.EDITABLETEXT:
  			clsList.insert(0, AccessibleEditField)

  class AccessibleEditField(Window):
  	"""Make edit fields more accessible."""
  	
  	def _get_description(self):
  		"""Add helpful description."""
  		return "Editable text field"
  ```
</CodeGroup>

### Overlay Class Rules

<Warning>
  * Insert overlay classes at the **beginning** of `clsList` (index 0)
  * Overlay classes must inherit from an appropriate base class (usually the API class like `IAccessible` or `Window`)
  * Multiple overlay classes can be added; they're resolved in order
  * More specific classes should be inserted earlier
</Warning>

## Customizing Properties

Override property getters to customize object information:

<CodeGroup>
  ```python Basic Properties theme={null}
  from NVDAObjects.IAccessible import IAccessible
  import controlTypes

  class CustomControl(IAccessible):
  	"""Custom control with modified properties."""
  	
  	def _get_name(self):
  		"""Override name property."""
  		# Get name from custom location
  		name = self._getCustomName()
  		return name or super().name
  	
  	def _get_role(self):
  		"""Override role."""
  		return controlTypes.Role.BUTTON
  	
  	def _get_description(self):
  		"""Override description."""
  		return "This is a custom control"
  	
  	def _get_value(self):
  		"""Override value."""
  		return self._getCustomValue()
  	
  	def _get_states(self):
  		"""Override states."""
  		states = super().states
  		if self._isCustomSelected():
  			states.add(controlTypes.State.SELECTED)
  		return states
  ```

  ```python Advanced Properties theme={null}
  from NVDAObjects.behaviors import RowWithFakeNavigation
  from NVDAObjects.IAccessible import IAccessible
  import controlTypes

  class DataGridCell(RowWithFakeNavigation, IAccessible):
  	"""Custom data grid cell."""
  	
  	def _get_name(self):
  		"""Construct name from row and column headers."""
  		rowHeader = self._getRowHeader()
  		colHeader = self._getColumnHeader()
  		cellValue = super().name
  		return f"{rowHeader}, {colHeader}, {cellValue}"
  	
  	def _get_positionInfo(self):
  		"""Provide position information."""
  		return {
  			'indexInGroup': self._getColumnIndex(),
  			'similarItemsInGroup': self._getTotalColumns(),
  			'level': 1
  		}
  ```
</CodeGroup>

## Handling Events

Overlay classes can handle events specific to their instances:

```python events.py theme={null}
from NVDAObjects.IAccessible import IAccessible
import ui
import controlTypes

class CustomList(IAccessible):
	"""Custom list with enhanced event handling."""
	
	def event_gainFocus(self):
		"""Custom focus handling."""
		super().event_gainFocus()
		# Announce additional information
		count = self.childCount
		ui.message(f"{count} items in list")
	
	def event_selection(self):
		"""Handle selection changes."""
		super().event_selection()
		selectedCount = len(self.getSelectedChildren())
		ui.message(f"{selectedCount} items selected")
	
	def event_valueChange(self):
		"""Handle value changes."""
		# Announce in custom format
		ui.message(f"Value: {self.value}")
```

### Event Methods in Objects

When events are defined on NVDA Objects (not app modules/global plugins), the signature is different:

```python theme={null}
# In overlay class: only takes self
def event_gainFocus(self):
    # No obj or nextHandler parameters
    super().event_gainFocus()

# In app module/global plugin: takes obj and nextHandler  
def event_gainFocus(self, obj, nextHandler):
    # obj is the NVDA Object
    nextHandler()
```

## Adding Scripts

Add keyboard commands specific to certain controls:

```python scripts.py theme={null}
from NVDAObjects.IAccessible import IAccessible
from scriptHandler import script
import ui
import controlTypes

class CustomDataGrid(IAccessible):
	"""Data grid with custom navigation commands."""
	
	@script(
		description="Move to next column",
		gesture="kb:control+rightArrow"
	)
	def script_nextColumn(self, gesture):
		"""Move to next column in grid."""
		nextCell = self._getNextColumn()
		if nextCell:
			nextCell.setFocus()
		else:
			ui.message("Last column")
	
	@script(
		description="Read column header",
		gesture="kb:NVDA+shift+c"
	)
	def script_readColumnHeader(self, gesture):
		"""Announce current column header."""
		header = self._getColumnHeader()
		if header:
			ui.message(header)
		else:
			ui.message("No header")
	
	def _getNextColumn(self):
		"""Get next column cell."""
		# Implementation
		return None
	
	def _getColumnHeader(self):
		"""Get column header text."""
		# Implementation
		return None
```

## Initialization

Use `initOverlayClass` to initialize overlay classes:

```python initialization.py theme={null}
from NVDAObjects.IAccessible import IAccessible
import controlTypes

class CustomControl(IAccessible):
	"""Control that needs initialization."""
	
	def initOverlayClass(self):
		"""Initialize overlay class."""
		# Called after the object is fully constructed
		self.customData = {}
		self.bindGestures({
			"kb:enter": "activate",
			"kb:space": "activate"
		})
```

## Text Support

Implement text support for controls by providing a `TextInfo` class:

<CodeGroup>
  ```python Custom TextInfo theme={null}
  from NVDAObjects.IAccessible import IAccessible
  import textInfos.offsets

  class CustomControl(IAccessible):
  	"""Control with custom text support."""
  	
  	TextInfo = CustomTextInfo

  class CustomTextInfo(textInfos.offsets.OffsetsTextInfo):
  	"""TextInfo for custom control."""
  	
  	def _getStoryText(self):
  		"""Get all text content."""
  		# Extract text from control
  		return self.obj._getCustomText()
  	
  	def _getStoryLength(self):
  		"""Get total length."""
  		return len(self._getStoryText())
  	
  	def _getLineOffsets(self, offset):
  		"""Get start and end offsets for line at offset."""
  		text = self._getStoryText()
  		start = text.rfind('\n', 0, offset) + 1
  		end = text.find('\n', offset)
  		if end == -1:
  			end = len(text)
  		return (start, end)
  ```

  ```python Using Existing TextInfo theme={null}
  from NVDAObjects.window import Window
  from NVDAObjects.window.edit import EditTextInfo

  class CustomEdit(Window):
  	"""Custom edit control using standard EditTextInfo."""
  	
  	TextInfo = EditTextInfo
  	
  	def _get_role(self):
  		return controlTypes.Role.EDITABLETEXT
  ```
</CodeGroup>

## Behaviors

NVDA provides behavior mixins for common functionality:

```python behaviors.py theme={null}
from NVDAObjects.behaviors import (
	EditableText,
	Dialog,
	RowWithFakeNavigation,
	KeyboardHandlerBasedTypedCharSupport
)
from NVDAObjects.IAccessible import IAccessible

class CustomEdit(EditableText, IAccessible):
	"""Editable text with caret tracking."""
	pass

class CustomDialog(Dialog, IAccessible):
	"""Dialog with special handling."""
	pass

class CustomRow(RowWithFakeNavigation, IAccessible):
	"""Table row with arrow key navigation."""
	pass

class CustomTerminal(KeyboardHandlerBasedTypedCharSupport, IAccessible):
	"""Terminal with typed character support."""
	pass
```

### Common Behaviors

<ParamField path="EditableText" type="mixin">
  Provides caret tracking and text editing support
</ParamField>

<ParamField path="Dialog" type="mixin">
  Dialog-specific functionality
</ParamField>

<ParamField path="ProgressBar" type="mixin">
  Progress bar announcement logic
</ParamField>

<ParamField path="RowWithFakeNavigation" type="mixin">
  Enables row/column navigation with arrow keys
</ParamField>

<ParamField path="KeyboardHandlerBasedTypedCharSupport" type="mixin">
  Reports typed characters in applications that don't fire character events
</ParamField>

## Practical Examples

### Example 1: Custom Button with Icon

```python customButton.py theme={null}
from NVDAObjects.IAccessible import IAccessible
import controlTypes
import ui

class IconButton(IAccessible):
	"""Button that displays only an icon, needs text name."""
	
	def _get_name(self):
		"""Get name from tooltip or aria-label."""
		# Try aria-label first
		ariaLabel = self._getAriaLabel()
		if ariaLabel:
			return ariaLabel
		
		# Fall back to tooltip
		tooltip = self.description
		if tooltip:
			return tooltip
		
		# Last resort: use role
		return "Button"
	
	def _get_role(self):
		"""Ensure role is button."""
		return controlTypes.Role.BUTTON
	
	def _getAriaLabel(self):
		"""Extract aria-label attribute."""
		# Implementation depends on control type
		return None
```

### Example 2: Enhanced List Item

```python enhancedList.py theme={null}
from NVDAObjects.IAccessible import IAccessible
import controlTypes
import ui

class EnhancedListItem(IAccessible):
	"""List item with rich content."""
	
	def _get_name(self):
		"""Construct name from multiple elements."""
		parts = []
		
		# Get main text
		mainText = self._getMainText()
		if mainText:
			parts.append(mainText)
		
		# Get secondary info
		secondary = self._getSecondaryInfo()
		if secondary:
			parts.append(secondary)
		
		# Get badge/status
		status = self._getStatus()
		if status:
			parts.append(f"Status: {status}")
		
		return ", ".join(parts)
	
	def _get_positionInfo(self):
		"""Provide position in list."""
		parent = self.parent
		if not parent:
			return None
		
		index = self._getIndex()
		total = parent.childCount
		
		return {
			'indexInGroup': index,
			'similarItemsInGroup': total
		}
	
	def _getMainText(self):
		"""Get main text from control."""
		return super().name
	
	def _getSecondaryInfo(self):
		"""Get secondary information."""
		# Extract from child elements
		return None
	
	def _getStatus(self):
		"""Get status indicator."""
		return None
	
	def _getIndex(self):
		"""Get item index."""
		return 1
```

### Example 3: Custom Tree Item

```python customTree.py theme={null}
from NVDAObjects.IAccessible import IAccessible
from scriptHandler import script
import controlTypes
import ui

class CustomTreeItem(IAccessible):
	"""Tree item with custom navigation."""
	
	def _get_positionInfo(self):
		"""Position info with level."""
		info = super().positionInfo or {}
		info['level'] = self._getLevel()
		return info
	
	def _getLevel(self):
		"""Calculate tree level."""
		level = 1
		parent = self.parent
		while parent and parent.role == controlTypes.Role.TREEVIEWITEM:
			level += 1
			parent = parent.parent
		return level
	
	@script(
		description="Expand or collapse tree item",
		gesture="kb:space"
	)
	def script_toggleExpand(self, gesture):
		"""Toggle expanded state."""
		if controlTypes.State.EXPANDED in self.states:
			self._collapse()
			ui.message("Collapsed")
		else:
			self._expand()
			ui.message("Expanded")
	
	def _expand(self):
		"""Expand tree item."""
		self.doAction()
	
	def _collapse(self):
		"""Collapse tree item."""
		self.doAction()
```

### Example 4: Custom Data Table

```python dataTable.py theme={null}
from NVDAObjects.IAccessible import IAccessible
from scriptHandler import script
import controlTypes
import ui

class DataTableCell(IAccessible):
	"""Table cell with row/column headers."""
	
	def _get_name(self):
		"""Include row and column headers."""
		parts = []
		
		# Row header
		rowHeader = self._getRowHeader()
		if rowHeader:
			parts.append(rowHeader)
		
		# Column header
		colHeader = self._getColumnHeader()
		if colHeader:
			parts.append(colHeader)
		
		# Cell value
		value = super().name
		if value:
			parts.append(value)
		
		return ", ".join(parts)
	
	@script(
		description="Read row header",
		gesture="kb:NVDA+shift+r"
	)
	def script_readRowHeader(self, gesture):
		"""Announce row header."""
		header = self._getRowHeader()
		if header:
			ui.message(f"Row: {header}")
		else:
			ui.message("No row header")
	
	@script(
		description="Read column header",
		gesture="kb:NVDA+shift+c"
	)
	def script_readColumnHeader(self, gesture):
		"""Announce column header."""
		header = self._getColumnHeader()
		if header:
			ui.message(f"Column: {header}")
		else:
			ui.message("No column header")
	
	def _getRowHeader(self):
		"""Get row header text."""
		# Implementation specific to table structure
		return None
	
	def _getColumnHeader(self):
		"""Get column header text."""
		# Implementation specific to table structure
		return None
```

## Caching Properties

Use `_cache` dictionary for expensive computations:

```python caching.py theme={null}
from NVDAObjects.IAccessible import IAccessible

class ExpensiveControl(IAccessible):
	"""Control with expensive property calculations."""
	
	def _get_name(self):
		"""Name with caching."""
		try:
			return self._cache['name']
		except KeyError:
			name = self._calculateExpensiveName()
			self._cache['name'] = name
			return name
	
	def _calculateExpensiveName(self):
		"""Expensive name calculation."""
		# Complex processing
		return "Calculated name"
	
	def event_nameChange(self):
		"""Clear cache when name changes."""
		if 'name' in self._cache:
			del self._cache['name']
		super().event_nameChange()
```

## API-Specific Base Classes

Different accessibility APIs require different base classes:

```python apiClasses.py theme={null}
# IAccessible/MSAA objects
from NVDAObjects.IAccessible import IAccessible
class MyIAccessibleControl(IAccessible):
    pass

# UI Automation objects
from NVDAObjects.UIA import UIA
class MyUIAControl(UIA):
    pass

# Java Access Bridge objects
from NVDAObjects.JAB import JAB
class MyJABControl(JAB):
    pass

# Window objects (direct Windows API)
from NVDAObjects.window import Window
class MyWindowControl(Window):
    pass
```

## Testing

<Steps>
  <Step title="Create app module or global plugin">
    Implement `chooseNVDAObjectOverlayClasses` method.
  </Step>

  <Step title="Define overlay classes">
    Create your custom NVDA Object classes.
  </Step>

  <Step title="Place in scratchpad">
    Put files in appropriate scratchpad directory.
  </Step>

  <Step title="Enable NVDA speech viewer">
    Tools > Speech Viewer to see what NVDA announces.
  </Step>

  <Step title="Test with object navigation">
    Use NVDA object navigation to examine objects.
  </Step>
</Steps>

## Debugging

<CodeGroup>
  ```python Developer Info theme={null}
  # Press NVDA+F1 on any object to see developer info
  # Or use script:
  from scriptHandler import script
  import ui
  import api

  @script(gesture="kb:NVDA+shift+d")
  def script_debugInfo(self, gesture):
      obj = api.getNavigatorObject()
      info = []
      info.append(f"Name: {obj.name}")
      info.append(f"Role: {obj.role}")
      info.append(f"Class: {obj.__class__.__name__}")
      info.append(f"WindowClass: {obj.windowClassName}")
      ui.message(", ".join(info))
  ```

  ```python Logging theme={null}
  import logging
  from NVDAObjects.IAccessible import IAccessible

  log = logging.getLogger(__name__)

  class DebugControl(IAccessible):
      
      def _get_name(self):
          name = super().name
          log.debug(f"Getting name for {self.windowClassName}: {name}")
          return name
      
      def event_gainFocus(self):
          log.info(f"Focus gained: {self.name}")
          super().event_gainFocus()
  ```
</CodeGroup>

## Best Practices

<Warning>
  **Performance:**

  * Cache expensive computations
  * Don't perform long operations in property getters
  * Clear caches appropriately when properties change
  * Use `_cache` dictionary for caching
</Warning>

<Note>
  **Code Quality:**

  * Always call `super()` methods unless overriding completely
  * Provide meaningful names and descriptions
  * Test with multiple applications
  * Handle missing/None values gracefully
  * Document complex logic
</Note>

## Common Patterns

```python patterns.py theme={null}
# Pattern 1: Name from multiple sources
def _get_name(self):
    return (
        self._getAriaLabel() or
        self._getTooltip() or
        super().name or
        "Unlabeled control"
    )

# Pattern 2: Add to existing states
def _get_states(self):
    states = super().states
    if self._isCustomCondition():
        states.add(controlTypes.State.CHECKED)
    return states

# Pattern 3: Format position info
def _get_positionInfo(self):
    info = super().positionInfo or {}
    info.update({
        'indexInGroup': self._getIndex(),
        'similarItemsInGroup': self._getTotal()
    })
    return info

# Pattern 4: Combine roles
def _get_role(self):
    baseRole = super().role
    if self._hasSpecialBehavior():
        return controlTypes.Role.BUTTON
    return baseRole
```

## Related Resources

* [App Modules →](/development/addons/app-modules)
* [Global Plugins →](/development/addons/global-plugins)
* [API Reference →](/development/addons/api-reference)
