Skip to content

Nautobot UI Component Framework

Table of Contents

Introduction

The Nautobot UI Framework revolutionizes how you create object detail views in your applications. Instead of writing HTML templates, you declaratively define your UI structure using Python objects, resulting in consistent, maintainable, and responsive interfaces.

  • UI Framework Example

Why Use the UI Framework?

  • Reduced Development Time: Eliminate boilerplate HTML/CSS
  • 🎯 Consistency: Automatic adherence to Nautobot's design patterns
  • 🔄 Reusable Components: Compose views from pre-built panels
  • 🛠 Extensible: Easy to customize and extend

Getting Started

Basic Setup

  1. Create a view that inherits from NautobotUIViewSet:
from nautobot.apps import views
from nautobot.apps.ui import ObjectDetailContent, SectionChoices, ObjectFieldsPanel

class ExampleUIViewSet(views.NautobotUIViewSet):
    queryset = Example.objects.all()
    ...

    object_detail_content = ObjectDetailContent(
        panels=[
            ObjectFieldsPanel(
               weight=100,
               section=SectionChoices.LEFT_HALF,
                fields="__all__"
            )
        ]
    )

Core Concepts

ObjectDetailContent Definition

Each object detail view (whether based on a NautobotUIViewSet or a legacy ObjectView) that implements the UI Framework does so by defining an object_detail_content attribute which is an instance of the ObjectDetailContent class. This class automatically does a lot of the "heavy lifting" for you as a developer, but you'll generally want to configure it for your specific data model, usually by defining any of the following:

  • Extra Tabs (beyond the default "main", "advanced", "contacts", etc. tabs that are automatically provided)
  • Panels in the "main" tab or any extra tabs
  • Extra Buttons to display at the top of the page (beyond the default "actions" dropdown)

Tabs

A Tab is one of the major building blocks of your UI. The user can toggle between tabs, each of which displays distinct page content, typically consisting of one or more Panels. Multiple tabs can be rendered in a single HTML response (for example, the "main" and "advanced" tabs are both part of the same page; toggling between them is a client-side action only and does not require Nautobot to reload or re-render the page), while to optimize performance, certain other tabs may correspond to distinct Views that are retrieved and rendered on request. The UI Framework supports both patterns.

Panels

Panels are another of the major building blocks of your UI. They contain specific types of content and can be positioned within sections within a Tab.

  • Basic Panel Layout

Buttons

Buttons are the third major building block for the UI. Defining extra_buttons on your ObjectDetailContent instance allows you to add buttons that appear at the top of the page, alongside the standard "actions" dropdown and any custom buttons added by Apps. These buttons may operate as simple hyperlinks, or may use JavaScript to provide more advanced functionality.

Buttons Example

from nautobot.apps.ui import ObjectDetailContent, Button

object_detail_content = ObjectDetailContent(
        panels=[...],
        extra_buttons=[
            Button(
                weight=100,
                label="Check Secret",
                icon="mdi-test-tube",
                javascript_template_path="extras/secret_check.js",
                attributes={"onClick": "checkSecret()"},
            ),
        ],
    )
  • Buttons Example

Page headings and document titles

The Titles component provides a flexible system for generating page titles and headings based on the current view action and context.

from nautobot.apps.ui import Titles

# Create a document title instance
titles = Titles(titles={"list": "{{obj_type_plural}}"})

# Render title for a list view
context = Context({
    'view_action': 'list',
    'obj_type_plural': 'devices',
    'view_titles': titles,
})

title = titles.render(context)  # Returns: "Devices"

In HTML there is available render_title helper that use the Title instance from context['view_titles'] and properly renders the page heading and document title.

{% block content %}
    <h1>{% block title %}{% render_title %}{% endblock %}</h1>
{% endblock %}

The Breadcrumbs component creates navigation trails that help users understand their location within the application hierarchy.

There are 3 main breadcrumb items classes: - ViewNameBreadcrumbItem - Handles breadcrumbs that are generated from Django view names using URL reversing. - ModelBreadcrumbItem - Generates breadcrumbs from Django model metadata, automatically creating appropriate URLs and labels. - InstanceBreadcrumbItem - Creates detail breadcrumbs for specific object instances, generating URLs to the object's detail page. - BaseBreadcrumbItem - Can be used to create custom breadcrumb items or to show just empty "label" within the breadcrumbs path.

By default, breadcrumbs class will add to the breadcrumbs path following items: - link to the list_url at the beginning - link to view the object details at the end

from nautobot.apps.ui import Breadcrumbs, ViewNameBreadcrumbItem, ModelBreadcrumbItem, InstanceBreadcrumbItem

# Use default breadcrumbs
breadcrumbs = Breadcrumbs()

# Render for a device detail view
context = Context({
    'view_action': 'retrieve',
    'detail': True,  # Indicates this is a detail view
    'object': device,
})
html = breadcrumbs.render(context)

It will generate:

<ol class="breadcrumb">
    <li><a href="/dcim/devices">Devices</a></li>
    <li><a href="/dcim/devices/<uuid>">Device name</a></li>
</ol>

Important

By adding custom items you're actually extending the default paths.

from nautobot.apps.ui import Breadcrumbs, ViewNameBreadcrumbItem, ModelBreadcrumbItem, InstanceBreadcrumbItem

# Use default breadcrumbs
breadcrumbs = Breadcrumbs(items={"detail": [
    ViewNameBreadcrumbItem(viewname_str="home", label="Home"),
    ModelBreadcrumbItem(model_key="location"),
]})

# Render for a device detail view
context = Context({
    'view_action': 'retrieve',
    'detail': True,  # Indicates this is a detail view
    'object': device,
    'location': device.location,
})
html = breadcrumbs.render(context)

It will generate:

<ol class="breadcrumb">
    <li><a href="/dcim/devices">Devices</a></li>
    <li><a href="/">Home</a></li>
    <li><a href="/dcim/locations">Locations</a></li>
    <li><a href="/dcim/devices/<uuid>">Device name</a></li>
</ol>

Panel Types

Base Panel

The Panel component serves as a base class for creating individual display panels within a Layout system. You'll rarely use it directly, but it's important to understand as the base class for the more feature-filled subclasses described below.

Code reference

Panel Examples

from nautobot.apps.ui import Panel, SectionChoices

Panel(
   weight=100,
   section=SectionChoices.FULL_WIDTH,
   label="Panel Header",
)
from nautobot.apps.ui import Panel, SectionChoices

Panel(
    weight=200,
    section=SectionChoices.RIGHT_HALF,
    label="Optional Params Included",
    body_content_template_path="path/to/template/body_content_template.html",
    header_extra_content_template_path="path/to/template/header_extra_content_template.html",
    footer_content_template_path="path/to/template/footer_content_template.html",
    template_path="path/to/template/template.html",
    body_wrapper_template_path="path/to/template/body_wrapper_template.html",
)

ObjectFieldsPanel

ObjectFieldsPanel is designed to automatically render object attributes in a table format. It's particularly useful for displaying model instances or any object with defined attributes. This panel inherits from KeyValueTablePanel.

Code reference

NOTE: When fields="__all__", the panel automatically excludes:

- ManyToMany fields and reverse relations (these should be displayed via `ObjectsTablePanel` if desired)
- hidden fields (name starting with `_` or explicitly declared as `hidden=True`)
- `id`, `created`, `last_updated` (these are automatically displayed elsewhere in the standard view template)
- `comments`, `tags` (these are automatically added as standalone panels)

ObjectFieldsPanel Examples

from nautobot.apps.ui import ObjectFieldsPanel, SectionChoices

ObjectFieldsPanel(
   weight=100,
   section=SectionChoices.LEFT_HALF,
   label="Object Fields Panel",
   context_object_key="obj",
)
from nautobot.apps import ui
from nautobot.core.templatetags import helpers


panels = ui.ObjectFieldsPanel(
    weight=200,
    section=ui.SectionChoices.LEFT_HALF,
    label="Object Fields Panel",
    fields=["name", "number", "notes", "notexists"],
    context_object_key="obj",
    ignore_nonexistent_fields=True,
    value_transforms={
        "name": [helpers.bettertitle],
        "number": [lambda v: helpers.hyperlinked_field(v, "https://nautobot.com")]
    },
),
  • ObjectFieldsPanel Example
  • ObjectFieldsPanel Example

KeyValueTablePanel

KeyValueTablePanel is a Panel component that displays data in a two-column table format, commonly used in object detail views. It extends the base Panel class and provides additional functionality for data display and transformation.

Tip

For displaying the attributes of a specific data model instance, you'll more commonly want to use the ObjectFieldsPanel subclass rather than using a KeyValueTablePanel directly.

Code reference

KeyValueTablePanel Examples

from nautobot.apps.ui import KeyValueTablePanel

KeyValueTablePanel(
    weight=100,
    data={
        "speed": "1000000",
        "notes": "**Important**"
    },
)
from nautobot.apps.ui import KeyValueTablePanel

KeyValueTablePanel(
    weight=100,
    data={
        "speed": "1000000",
        "notes": "**Important**",
    },
    hide_if_unset=(),
    value_transforms={
        "speed": [             # List of functions to apply in order
            humanize_speed,    # Convert 1000000 to "1 Gbps"
            placeholder        # Show placeholder if empty
        ],
        "notes": [
            render_markdown,   # Convert markdown to HTML
            placeholder       # Show placeholder if empty
        ]
    },
)

GroupedKeyValueTablePanel

GroupedKeyValueTablePanel is a specialized version of KeyValueTablePanel that organizes data into collapsible accordion groups. This is used in the standard template for displaying grouped custom/computed fields when present.

Code reference

GroupedKeyValueTablePanel Examples

from nautobot.apps.ui import GroupedKeyValueTablePanel, SectionChoices

GroupedKeyValueTablePanel(
    weight=300,
    section=SectionChoices.FULL_WIDTH,
    label="Grouped Information",
    body_id="network-details",
    # Data Structure
    data={
        "Network": {            # Group name (shown as accordion header)
            "VLAN": "100",      # Key-value pairs in this group
            "IP Range": "192.168.1.0/24"
        },
        "Physical": {           # Another group
            "Location": "Rack A1",
            "Height": "2U"
        },
        "": {                   # Empty string for ungrouped items
            "Notes": "Important info"
        }
    },
)
  • GroupedKeyValueTablePanel Example 1

  • GroupedKeyValueTablePanel Example 2

StatsPanel

StatsPanel is a Panel component that displays statistical information with clickable links to filtered views of related models. It's particularly useful for dashboards and summary views that provide quick access to filtered data sets.

Code reference

StatsPanel Examples

from nautobot.apps.ui import StatsPanel, SectionChoices

StatsPanel(
    weight=700,
    section=SectionChoices.RIGHT_HALF,
    label="Statistics",
    filter_name="location",
    related_models=[  # Models to show statistics for
        Device,  # Direct model reference
                 # Will count all devices related to this object by their `location` key
        (Circuit, "circuit_terminations__location__in"),
                 # Tuple of (Model, query_string)
                 # For complex relationships
        (VirtualMachine, "cluster__location__in")
                 # Another complex relationship example
    ],
)
  • StatsPanel Code Example
  • StatsPanel Example

BaseTextPanel

BaseTextPanel is a specialized Panel component designed to display single values in various text formats including plaintext, JSON, YAML, Markdown, and code snippets.

Code reference

  • Text Panels Family

ObjectTextPanel

ObjectTextPanel renders content from a specific field of an object in the context. It simplifies the display of object attributes in various text formats (Markdown, JSON, YAML, etc.).

Code reference

ObjectTextPanel Examples

from nautobot.apps.ui import ObjectTextPanel, SectionChoices

ObjectTextPanel(
   weight=500,
   section=SectionChoices.FULL_WIDTH,
   label="Description",
   object_field="description",
   render_as=ObjectTextPanel.RenderOptions.MARKDOWN,
   render_placeholder=True,
)

TextPanel

TextPanel renders content from a specified context field. It provides a simple way to display text content in various formats (Markdown, JSON, YAML, plaintext, or code) from the rendering context.

Code reference

TextPanel Examples

from nautobot.apps.ui import TextPanel, SectionChoices

TextPanel(
   weight=600,
   section=SectionChoices.FULL_WIDTH,
   label="Custom Content",
   context_field="text",
   render_as=TextPanel.RenderOptions.CODE,
   render_placeholder=True,
)

DataTablePanel

DataTablePanel is a Panel component that renders tabular data directly from a list of dictionaries, providing a lightweight alternative to django_tables2 Table classes.

Code reference

Note: columns/context_columns_key and column_headers/context_column_headers_key are mutually exclusive pairs.

  • Table Panels Family

DataTablePanel Examples

from nautobot.apps.ui import DataTablePanel

DataTablePanel(
   weight=100,
   context_data_key="data",
   columns=["one", "two", "three"],
   column_headers=["One", "Two", "Three"]
)

ObjectsTablePanel

The ObjectsTablePanel is a powerful component for rendering tables of Django model objects, particularly suited for displaying related objects within a detail view. It integrates with django_tables2 and provides extensive customization options.

Configuration Parameters

Code reference

Core Configuration
Parameter Required Default Description
context_table_key No None Key for pre-configured table in render context
table_class No None Table class to instantiate (e.g., DeviceTable)
table_filter No None Filter name for queryset (e.g., location_type)
table_attribute No None Object attribute containing queryset
table_title No Model's plural name Title displayed in panel heading
Query Optimization
Parameter Required Default Description
select_related_fields No None Fields to include in select_related()
prefetch_related_fields No None Fields to include in prefetch_related()
order_by_fields No None Fields to order queryset by
max_display_count No User preference Maximum items to display
Column Configuration
Parameter Required Default Description
include_columns No None List of columns to display
exclude_columns No None List of columns to hide
hide_hierarchy_ui No False Disable tree model indentation
Actions Configuration
Parameter Required Default Description
add_button_route No "default" Route for "add" button URL
add_permissions No Model defaults Required permissions for "add" button
enable_bulk_actions No False Enable bulk action checkboxes
related_field_name No table_filter value Field linking to base model

ObjectsTablePanel Examples

from nautobot.apps.ui import ObjectsTablePanel, SectionChoices

ObjectsTablePanel(
    weight=100,
    section=SectionChoices.RIGHT_HALF,
    table_class=ExampleTable,
    table_filter="example_name",
    table_title="Example Table",

)
from nautobot.apps.ui import ObjectsTablePanel, SectionChoices

ObjectsTablePanel(
    weight=200,
    section=SectionChoices.FULL_WIDTH,
    context_table_key="example_table_instance",
    table_title="Example Table",
    hide_hierarchy_ui=True,
    related_field_name="example_id",
    enable_bulk_actions=True,
    add_permissions=["dcim:add_device"],
    max_display_count=10,
    prefetch_related_fields=["devices"],
    select_related_fields=["manufacturers", "locations"],
)

Button Types

Button

The Button component defines a single button in an object detail view.

Code reference

Button Examples

class SecretUIViewSet(...):
    ...
    object_detail_content = ObjectDetailContent(
        panels=[...],
        extra_buttons=[
            Button(
                weight=100,
                label="Check Secret",
                icon="mdi-test-tube",
                javascript_template_path="extras/secret_check.js",
                attributes={"onClick": "checkSecret()"},
            ),
        ],
    )
  • Button Example

DropdownButton is a subclass of Button that may itself contain other Buttons as its children, which it will render as a dropdown menu. For an example of usage, refer to the Add Components dropdown on the Device detail view.

Code reference

class DeviceView(generic.ObjectView):
    ...
    object_detail_content = ObjectDetailContent(
        extra_buttons=[
            object_detail.DropdownButton(
                weight=100,
                color=ButtonColorChoices.BLUE,
                label="Add Components",
                icon="mdi-plus-thick",
                required_permissions=["dcim.change_device"],
                children=(
                    object_detail.Button(
                        weight=100,
                        link_name="dcim:device_consoleports_add",
                        label="Console Ports",
                        icon="mdi-console",
                        required_permissions=["dcim.add_consoleport"],
                    ),
                    object_detail.Button(
                        weight=200,
                        link_name="dcim:device_consoleserverports_add",
                        label="Console Server Ports",
                        icon="mdi-console-network-outline",
                        required_permissions=["dcim.add_consoleserverport"],
                    ),
                    ...
                ),
            ),
        ],
    )
  • DropdownButton Example

High-level data flow diagram:

  • Breadcrumbs and titles data flow

Breadcrumb items has three main type of items.

  • ViewNameBreadcrumbItem: Creates a breadcrumb from a Django view name (string or callable), with optional URL kwargs and query params. The label can be static or dynamic.
  • ModelBreadcrumbItem: Generates a breadcrumb based on a Django model class, instance, or dotted model string. Supports resolving list/detail URLs and model verbose names for the label.
  • InstanceBreadcrumbItem: Represents a breadcrumb for a specific object instance (fetched from context). The URL and label are resolved from the object’s absolute URL and display/name.

All breadcrumb items accept a should_render callable to control visibility based on context, and are automatically filtered out if both their URL and label are empty. Labels can be rendered either by using simple str, passing object that has __str__ method, passing callable that accepts context or by taking it from context via label_key.

ViewNameBreadcrumbItem Class

# Basic view name breadcrumb
item = ViewNameBreadcrumbItem(
    view_name="dcim:device_list",
    label="All Devices",
)
# Will generate: ("/dcim/devices/", "All Devices")

# Basic view name from context breadcrumb
item = ViewNameBreadcrumbItem(
    view_name_key="list_url",
    label="All Devices",
)
# Will generate: ("/dcim/devices/", "All Devices"); assuming there is "dcim:device_list" in context["list_url"]

# With reverse parameters
item = ViewNameBreadcrumbItem(
    view_name="dcim:device",
    reverse_kwargs={"pk": 123},
    label="Device Details",
)
# Will generate: ("/dcim/devices/123", "Device Details")

# With query parameters
item = ViewNameBreadcrumbItem(
    view_name="dcim:device_list",
    reverse_query_params={"status": "active"},
    label="Active Devices",
)
# Will generate: ("/dcim/devices/?status=active", "Active Devices")

# With dynamic parameters from context
item = ViewNameBreadcrumbItem(
    view_name="dcim:device_detail",
    reverse_kwargs=lambda ctx: {"pk": ctx["object"].pk},
    label=lambda ctx: f"Device: {ctx['object'].name}",
)

Code reference

ModelBreadcrumbItem Class

# Basic model breadcrumb (uses plural verbose_name)
item = ModelBreadcrumbItem(model=Device)
# Will generate: ("/dcim/devices/", "Devices")

# Singular form
item = ModelBreadcrumbItem(
    model=Device,
    model_label_type="singular",
)
# Will generate: ("/dcim/devices/", "Device")

# Different URL action
item = ModelBreadcrumbItem(
    model=Device,
    model_url_action="add",
)
# Will generate: ("/dcim/devices/add/", "Devices")

item = ModelKeyBreadcrumbItem(
    model_key="parent_model",
    model_label_type="singular",
)
# Will generate: ("/dcim/devices/", "Device") - assuming that "parent model" is Device

# Using dotted model path
item = ModelBreadcrumbItem(model="dcim.device")
# Will generate: ("/dcim/devices/", "Devices")

Code reference

InstanceBreadcrumbItem Class

# Basic usage - looks for instance in context["object"]
item = InstanceBreadcrumbItem()
# Uses default instance_key="object"

# Custom context key
item = InstanceBreadcrumbItem(instance_key="device")

# Custom label
item = InstanceBreadcrumbItem(
    instance_key="object",
    label="Current Item"
)

# Dynamic label from context
item = InstanceBreadcrumbItem(
    label=lambda ctx: f"Editing {ctx['object'].name}"
)

Code reference

Customizing Breadcrumbs

By default Breadcrumbs class uses two actions: list and detail. However, you can specify your custom action, or just pass to the items one built-in action to override default only for the chosen view actions.

If there is no view_action in the context it will use list by default.

If there is no detail in the context it will assume detail=False by default.

Code reference

Example 1: Simple Override

# Replace default breadcrumbs for create action
custom_breadcrumbs = Breadcrumbs(
    items={
        'create': [
            ViewNameBreadcrumbItem(view_name="home", label="Home"),
            ModelBreadcrumbItem(model_key="model"),
            ModelBreadcrumbItem(model_key="model", action="add", label="Add New")
        ]
    }
)

Example 2: Complex Navigation

# Multi-level hierarchy for nested objects
nested_breadcrumbs = Breadcrumbs(
    items={
        'retrieve': [
            ModelBreadcrumbItem(model="dcim.location", model_label_type="plural"),
            InstanceBreadcrumbItem(
                instance_key="object.location",
                label=lambda ctx: f"Site: {ctx['object'].location.name}"
            ),
            ModelBreadcrumbItem(model="dcim.device", model_label_type="plural"),
            InstanceBreadcrumbItem(),
        ]
    }
)

Complete Example

views.py
from nautobot.apps.ui import (
    SectionChoices,
    ObjectFieldsPanel,
    ObjectDetailContent,
    StatsPanel,
)
from nautobot.apps import views
from your_app.models import Location, Device, Circuit

class LocationUIViewSet(views.NautobotUIViewSet):
    queryset = Location.objects.all()

    object_detail_content = ObjectDetailContent(
        panels=[
            ObjectFieldsPanel(
               weight=100,
               section=SectionChoices.LEFT_HALF,
               fields="__all__",
            ),
            StatsPanel(
                weight=100,
                section=SectionChoices.RIGHT_HALF,
                filter_name="location",
                related_models=[
                    Device,
                    (Circuit, "circuit_terminations__location__in")
                ],
            ),
            GroupedKeyValueTablePanel(
                weight=300,
                section=SectionChoices.FULL_WIDTH,
                body_id="custom-fields",
                data={
                    "Network": {
                        "VLAN": "100",
                        "IP Range": "192.168.1.0/24"
                    }
                },
            ),
            ObjectTextPanel(
                weight=400,
                section=SectionChoices.FULL_WIDTH,
                object_field="description",
                render_as=BaseTextPanel.RenderOptions.MARKDOWN,
            ),
        ]
    )

Layouts and Sections

Render Options

Section Options

Best Practices

1. Panel Organization

  • Use consistent weights across your application
  • Group related information in adjacent panels
  • Consider various screen sizes when choosing sections, especially for ObjectsTablePanel

2. Performance

  • Be specific with field selections - select only needed fields for better performance
  • Ensure usage of select_related or prefetch_related

3. User Experience

  • Provide clear, descriptive labels
  • Use consistent patterns across views
  • Implement proper error handling for custom business logic if writing custom panels

4. Maintenance

  • Document custom transformations
  • Keep related model lists updated
  • Use meaningful body_id values

Troubleshooting

Common Issues and Solutions

  1. Panels Not Appearing

    • Verify section choices
    • Check panel weights
    • Confirm data availability
  2. Performance Issues

    • Review query complexity in StatsPanel
    • Optimize field selections
    • Check database indexes
  3. Layout Problems

    • Validate section assignments
    • Review weight ordering
    • Check responsive behavior
  4. Value Rendering

    • Verify transform functions
    • Check data types
    • Confirm template paths