Table of Contents

Typed IDs for UI Elements

Neos provides strongly-typed identifiers for UI elements to improve code safety, maintainability, and developer experience. These typed IDs replace hard-coded strings and nameof() expressions throughout the codebase.

Overview

The framework automatically generates several static classes containing typed identifiers during the build process:

  • UIViews - Contains identifiers for all UI views in your application
  • Images - Contains identifiers for all images and icons
  • Reports - Contains identifiers for all reports
  • Themes - Contains identifiers for all themes

Benefits

Type Safety

Compile-time validation prevents typos and ensures referenced elements actually exist in your project.

// Compile error if CustomerListUI doesn't exist
new NavigationOptions(UIViews.CustomerListUI)

// Runtime error possible with strings
new NavigationOptions("CustomerListUI") // Typo won't be caught at compile time

Dependency Detection

Typed IDs enable the Dependency Viewer in Neos Studio to more reliably detect and analyze dependencies between elements. While simple string literals can be detected, typed IDs provide stronger guarantees and make complex scenarios easier to track.

// ✅ Always reliably detected by dependency analysis
new NavigationOptions(UIViews.CustomerListUI)

// ✅ Can be detected when using direct string literals
new NavigationOptions("CustomerListUI")

// ❌ Harder to track when using dynamic strings
string viewName = condition ? "CustomerListUI" : "CustomerEditUI";
new NavigationOptions(viewName)

// ✅ Better approach for conditional scenarios
NavigationOptions options = new(condition ? UIViews.CustomerListUI : UIViews.CustomerEditUI);

IntelliSense Support

Auto-completion helps discover available options and reduces the need to remember exact names.

Clear Documentation

The relationship between code and UI elements becomes explicit and self-documenting.

Available Typed ID Classes

UI views

Contains static readonly properties for all UI views in your application:

public static class UIViews
{
    /// <summary>
    /// CustomerListUI.
    /// </summary>
    public static readonly UIViewId CustomerListUI = null!;

    /// <summary>
    /// ProductEditUI.
    /// </summary>
    public static readonly UIViewId ProductEditUI = null!;

    // ... other UI views
}

Usage examples:

// Navigation
new NavigationOptions(UIViews.CustomerListUI)

Images

Contains static readonly properties for all images and icons:

public static class Images
{
    /// <summary>
    /// Settings.
    /// </summary>
    public static readonly ImageId Settings = null!;

    /// <summary>
    /// UserProfile.
    /// </summary>
    public static readonly ImageId UserProfile = null!;

    // ... other images
}

Usage examples:

// Icon assignment
.IconId = Images.Settings

// Method calls
.WithIconId(Images.UserProfile)

// Action icons
Actions.SaveData.IconId = Images.Save;

Reports

Contains static readonly properties for all reports:

public static class Reports
{
    /// <summary>
    /// InvoiceReport.
    /// </summary>
    public static readonly ReportId InvoiceReport = null!;

    /// <summary>
    /// CustomerStatement.
    /// </summary>
    public static readonly ReportId CustomerStatement = null!;

    // ... other reports
}

Usage examples:

// Report execution
await ExecuteReportOptions(Reports.InvoiceReport);

Themes

Contains static readonly properties for all themes:

public static class Themes
{
    /// <summary>
    /// NeosStudio.
    /// </summary>
    public static readonly ThemeId NeosStudio = null!;

    /// <summary>
    /// NeosStudioDark.
    /// </summary>
    public static readonly ThemeId NeosStudioDark = null!;

    // ... other themes
}

Usage examples:

// Theme switching
await ThemeManager.SetThemeAsync(Themes.NeosStudioDark);

// Theme comparison
if (ThemeManager.GetCurrentTheme().Id == Themes.NeosStudio.Name)
{
    // Light theme logic
}

Common Usage Patterns

Before (hard-coded strings):

new NavigationOptions("CustomerListUI")
new NavigationOptions(nameof(CustomerEditUI))

After (typed IDs):

new NavigationOptions(UIViews.CustomerListUI)
new NavigationOptions(UIViews.CustomerEditUI)

Icon Assignment

Before (string literals):

.IconName = "Settings"
.WithIcon("UserProfile")
Actions.Save.IconName = "save-icon";

After (typed IDs):

.IconId = Images.Settings
.WithIconId(Images.UserProfile)
Actions.Save.IconId = Images.SaveIcon;

Conditional Logic

Before:

if (currentView == "CustomerListUI")
{
    // Logic specific to customer list
}

After:

if (currentView == UIViews.CustomerListUI.Name)
{
    // Logic specific to customer list
}

Migration from Legacy Code

Identifying Legacy Patterns

Look for these patterns in your codebase:

  • new NavigationOptions("ViewName") → Use UIViews.ViewName
  • .IconName = "iconName" or .WithIcon("iconName") → Use Images.IconName and .WithIconId()
  • new NavigationOptions(nameof(ViewName)) → Use UIViews.ViewName

Automated Migration

For bulk conversion, use the PowerShell migration script available in the Neos repository:

Script Location: convert-ui-string-literals.ps1

This script serves as an example that you can adapt for your own cluster's migration needs.

Example usage:

# Navigate to your cluster directory
cd C:\YourCluster

# Download and adapt the script from the Neos repository
# Then run with preview mode first to see what changes would be made
.\convert-ui-string-literals.ps1 -Directories @("modules\YourModule1", "modules\YourModule2") -WhatIf

# Apply changes
.\convert-ui-string-literals.ps1 -Directories @("modules\YourModule1", "modules\YourModule2")

# Apply changes with backup and verbose output
.\convert-ui-string-literals.ps1 -Directories @("modules\YourModule1", "modules\YourModule2") -CreateBackup -VerboseOutput

Best Practices

Use Typed IDs Consistently

Always prefer typed IDs over string literals for better type safety, dependency tracking, and maintainability:

// ✅ Good - Type-safe and always trackable by dependency analysis
new NavigationOptions(UIViews.CustomerListUI)
.WithIconId(Images.Settings)

// ⚠️ Less reliable - Direct strings can be detected but dynamic scenarios are problematic
new NavigationOptions("CustomerListUI")
.WithIcon("settings")

// ❌ Avoid - Very difficult to track in dependency analysis
string viewName = condition ? "CustomerListUI" : "CustomerEditUI";
new NavigationOptions(viewName)

// ✅ Better approach for conditional scenarios
NavigationOptions options = new(condition ? UIViews.CustomerListUI : UIViews.CustomerEditUI);

Leverage IntelliSense

Take advantage of auto-completion to discover available options:

// Type "UIViews." and use IntelliSense to see all available views
var navigation = new NavigationOptions(UIViews.

Handle Dynamic Scenarios

For cases where UI element names are determined at runtime, use the FromName() method instead of string literals:

// ✅ Correct approach for dynamic names
string dynamicViewName = $"Customer{type}UI";
NavigationOptions options = new(UIViewId.FromName(dynamicViewName));

// Same pattern for other ID types
ImageId dynamicIconId = ImageId.FromName($"Status{status}Icon");
ReportId dynamicReportId = ReportId.FromName($"{category}Report");
Warning

Methods accepting string parameters directly (e.g., new NavigationOptions(string)) are obsolete and should be avoided. Always use the typed ID classes and their FromName() method for dynamic scenarios.