Files
Obsidian-Plugins/op.docs/PLUGIN_SETTINGS_UI_GUIDE.md
T
2026-06-19 16:52:38 -04:00

605 lines
15 KiB
Markdown

# Plugin Settings UI Guide
This guide explains how to structure and format plugin settings UI in Obsidian, with examples for building multiple plugins with distinct features.
## Table of Contents
1. [Core Concepts](#core-concepts)
2. [Setting Types](#setting-types)
3. [UI Organization Patterns](#ui-organization-patterns)
4. [Multi-Plugin Architecture](#multi-plugin-architecture)
5. [Plugin Examples](#plugin-examples)
## Core Concepts
### Settings Interface & Settings Tab
Every plugin has:
1. **Settings Interface** - TypeScript interface defining your data structure
2. **Settings Tab Class** - Extends `PluginSettingTab` and defines the UI
```typescript
// Define the data structure
export interface MyPluginSettings {
settingName: string;
settingValue: boolean;
}
// Default values
export const DEFAULT_SETTINGS: MyPluginSettings = {
settingName: 'default',
settingValue: false,
};
// UI definition
export class MySettingTab extends PluginSettingTab {
plugin: MyPlugin;
constructor(app: App, plugin: MyPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty(); // Clear previous UI
// Add UI elements here
}
}
```
### Container Structure
The `containerEl` is where you build your UI. Always start with `containerEl.empty()` to clear old content when the settings tab reopens.
```typescript
display(): void {
const { containerEl } = this;
containerEl.empty();
// Add heading
containerEl.createEl('h2', { text: 'Plugin Name' });
// Add settings
new Setting(containerEl)
.setName('Setting Name')
.setDesc('Description of what this does')
.addText((text) => { /* ... */ });
}
```
## Setting Types
### Text Input
Single-line text input:
```typescript
new Setting(containerEl)
.setName('Plugin Name')
.setDesc('The name of your plugin')
.addText((text) =>
text
.setPlaceholder('Enter name')
.setValue(this.plugin.settings.pluginName)
.onChange(async (value) => {
this.plugin.settings.pluginName = value;
await this.plugin.saveSettings();
})
);
```
### Text Area
Multi-line text input:
```typescript
new Setting(containerEl)
.setName('Configuration')
.setDesc('Multi-line configuration')
.addTextArea((text) =>
text
.setPlaceholder('Enter configuration')
.setValue(this.plugin.settings.config)
.onChange(async (value) => {
this.plugin.settings.config = value;
await this.plugin.saveSettings();
})
);
```
### Toggle
Boolean switch:
```typescript
new Setting(containerEl)
.setName('Enable Feature')
.setDesc('Turn this feature on or off')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.enabled)
.onChange(async (value) => {
this.plugin.settings.enabled = value;
await this.plugin.saveSettings();
})
);
```
### Dropdown
Select from predefined options:
```typescript
new Setting(containerEl)
.setName('Theme')
.setDesc('Choose a color theme')
.addDropdown((dropdown) =>
dropdown
.addOption('light', 'Light')
.addOption('dark', 'Dark')
.addOption('auto', 'Auto')
.setValue(this.plugin.settings.theme)
.onChange(async (value) => {
this.plugin.settings.theme = value;
await this.plugin.saveSettings();
})
);
```
### Slider
Numeric input with range:
```typescript
new Setting(containerEl)
.setName('Search Depth')
.setDesc('How deep to search in folders (1-10)')
.addSlider((slider) =>
slider
.setMin(1)
.setMax(10)
.setStep(1)
.setValue(this.plugin.settings.searchDepth)
.onChange(async (value) => {
this.plugin.settings.searchDepth = value;
await this.plugin.saveSettings();
})
);
```
### Button
Trigger an action:
```typescript
new Setting(containerEl)
.setName('Process Files')
.setDesc('Start processing all files')
.addButton((button) =>
button
.setButtonText('Process Now')
.onClick(async () => {
await this.plugin.processAllFiles();
new Notice('Processing complete!');
})
);
```
### Color Picker
Select a color:
```typescript
new Setting(containerEl)
.setName('Tag Color')
.setDesc('Color for tags')
.addColorPicker((color) =>
color
.setValue(this.plugin.settings.tagColor)
.onChange(async (value) => {
this.plugin.settings.tagColor = value;
await this.plugin.saveSettings();
})
);
```
## UI Organization Patterns
### Section Headers
Organize settings into logical sections:
```typescript
display(): void {
const { containerEl } = this;
containerEl.empty();
// Main heading
containerEl.createEl('h2', { text: 'Plugin Settings' });
// Section 1
containerEl.createEl('h3', { text: 'Basic Configuration' });
new Setting(containerEl).setName('Setting 1')...
new Setting(containerEl).setName('Setting 2')...
// Section 2
containerEl.createEl('h3', { text: 'Advanced Options' });
new Setting(containerEl).setName('Setting 3')...
new Setting(containerEl).setName('Setting 4')...
}
```
### Conditional Display
Show/hide settings based on other settings:
```typescript
display(): void {
const { containerEl } = this;
containerEl.empty();
new Setting(containerEl)
.setName('Enable Advanced')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.advancedMode)
.onChange(async (value) => {
this.plugin.settings.advancedMode = value;
await this.plugin.saveSettings();
this.display(); // Redraw to show/hide advanced settings
})
);
if (this.plugin.settings.advancedMode) {
new Setting(containerEl)
.setName('Advanced Option')
.setDesc('This only shows when advanced mode is on')
.addText((text) => { /* ... */ });
}
}
```
### Grouped Controls
Multiple controls in one setting:
```typescript
new Setting(containerEl)
.setName('Color')
.setDesc('Choose color and opacity')
.addColorPicker((color) =>
color
.setValue(this.plugin.settings.color)
.onChange(async (value) => {
this.plugin.settings.color = value;
await this.plugin.saveSettings();
})
)
.addSlider((slider) =>
slider
.setMin(0)
.setMax(100)
.setValue(this.plugin.settings.opacity)
.onChange(async (value) => {
this.plugin.settings.opacity = value;
await this.plugin.saveSettings();
})
);
```
## Multi-Plugin Architecture
When building multiple plugins within one codebase, consider this structure:
### Folder Structure
```
src/
main.ts (Plugin entry point)
settings.ts (Shared settings)
plugins/
folder/
folderPlugin.ts
folderSettings.ts
markdown/
markdownPlugin.ts
markdownSettings.ts
kanban/
kanbanPlugin.ts
kanbanSettings.ts
```
### Unified Settings Interface
```typescript
// settings.ts
export interface PluginConfig {
// Folder Plugin settings
folderPlugin: {
enabled: boolean;
organizationHierarchy: string;
};
// Markdown Plugin settings
markdownPlugin: {
enabled: boolean;
autoCreatePath: string;
template: string;
};
// Kanban Plugin settings
kanbanPlugin: {
enabled: boolean;
boardTemplate: string;
defaultLayout: string;
};
}
export const DEFAULT_SETTINGS: PluginConfig = {
folderPlugin: {
enabled: true,
organizationHierarchy: 'Category, Status',
},
markdownPlugin: {
enabled: true,
autoCreatePath: 'Notes',
template: 'default',
},
kanbanPlugin: {
enabled: false,
boardTemplate: 'default',
defaultLayout: 'columns',
},
};
```
### Unified Settings Tab
```typescript
// settings.ts
export class UnifiedSettingTab extends PluginSettingTab {
plugin: MultiPluginBase;
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'Plugin Suite Settings' });
// Folder Plugin Section
this.displayFolderPluginSettings(containerEl);
// Markdown Plugin Section
this.displayMarkdownPluginSettings(containerEl);
// Kanban Plugin Section
this.displayKanbanPluginSettings(containerEl);
}
displayFolderPluginSettings(containerEl: HTMLElement): void {
containerEl.createEl('h3', { text: 'Folder Organization Plugin' });
new Setting(containerEl)
.setName('Enable Folder Plugin')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.folderPlugin.enabled)
.onChange(async (value) => {
this.plugin.settings.folderPlugin.enabled = value;
await this.plugin.saveSettings();
})
);
if (this.plugin.settings.folderPlugin.enabled) {
new Setting(containerEl)
.setName('Organization Hierarchy')
.setDesc('Comma-separated frontmatter fields')
.addTextArea((text) =>
text
.setValue(this.plugin.settings.folderPlugin.organizationHierarchy)
.onChange(async (value) => {
this.plugin.settings.folderPlugin.organizationHierarchy = value;
await this.plugin.saveSettings();
})
);
}
}
displayMarkdownPluginSettings(containerEl: HTMLElement): void {
containerEl.createEl('h3', { text: 'Markdown Auto-Create Plugin' });
new Setting(containerEl)
.setName('Enable Markdown Plugin')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.markdownPlugin.enabled)
.onChange(async (value) => {
this.plugin.settings.markdownPlugin.enabled = value;
await this.plugin.saveSettings();
})
);
if (this.plugin.settings.markdownPlugin.enabled) {
new Setting(containerEl)
.setName('Auto-Create Path')
.setDesc('Default folder for auto-created files')
.addText((text) =>
text
.setPlaceholder('Notes')
.setValue(this.plugin.settings.markdownPlugin.autoCreatePath)
.onChange(async (value) => {
this.plugin.settings.markdownPlugin.autoCreatePath = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Template')
.setDesc('Template to use for new files')
.addDropdown((dropdown) =>
dropdown
.addOption('default', 'Default')
.addOption('minimal', 'Minimal')
.addOption('detailed', 'Detailed')
.setValue(this.plugin.settings.markdownPlugin.template)
.onChange(async (value) => {
this.plugin.settings.markdownPlugin.template = value;
await this.plugin.saveSettings();
})
);
}
}
displayKanbanPluginSettings(containerEl: HTMLElement): void {
containerEl.createEl('h3', { text: 'Kanban Board Plugin' });
new Setting(containerEl)
.setName('Enable Kanban Plugin')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.kanbanPlugin.enabled)
.onChange(async (value) => {
this.plugin.settings.kanbanPlugin.enabled = value;
await this.plugin.saveSettings();
})
);
if (this.plugin.settings.kanbanPlugin.enabled) {
new Setting(containerEl)
.setName('Board Template')
.setDesc('Default board template')
.addDropdown((dropdown) =>
dropdown
.addOption('default', 'Default')
.addOption('agile', 'Agile')
.addOption('gtd', 'Getting Things Done')
.setValue(this.plugin.settings.kanbanPlugin.boardTemplate)
.onChange(async (value) => {
this.plugin.settings.kanbanPlugin.boardTemplate = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Default Layout')
.setDesc('How boards are displayed by default')
.addDropdown((dropdown) =>
dropdown
.addOption('columns', 'Columns')
.addOption('rows', 'Rows')
.addOption('grid', 'Grid')
.setValue(this.plugin.settings.kanbanPlugin.defaultLayout)
.onChange(async (value) => {
this.plugin.settings.kanbanPlugin.defaultLayout = value;
await this.plugin.saveSettings();
})
);
}
}
}
```
## Plugin Examples
### Example 1: Folder Organization Plugin
**Settings Interface:**
```typescript
export interface FolderOrganizerSettings {
enabled: boolean;
organizationHierarchy: string;
createMissingFolders: boolean;
overwriteExisting: boolean;
excludeFolders: string[];
}
```
**Key UI Elements:**
- Toggle to enable/disable
- Text area for hierarchy configuration
- Toggle for auto-create folders
- Toggle for overwrite confirmation
- Multi-select or list for excluded folders
- Action button to organize files
### Example 2: Markdown Auto-Create Plugin
**Settings Interface:**
```typescript
export interface MarkdownAutoCreateSettings {
enabled: boolean;
defaultPath: string;
template: 'default' | 'minimal' | 'detailed';
autoOpenNewFile: boolean;
defaultFrontmatter: string;
hotkey: string;
}
```
**Key UI Elements:**
- Toggle to enable/disable
- Text input for default path
- Dropdown for template selection
- Toggle for auto-open
- Text area for frontmatter template
- Display current hotkey (or hotkey picker if available)
### Example 3: Kanban Board Plugin
**Settings Interface:**
```typescript
export interface KanbanBoardSettings {
enabled: boolean;
boardTemplate: 'default' | 'agile' | 'gtd';
defaultLayout: 'columns' | 'rows' | 'grid';
cardWidth: number;
showDates: boolean;
showTags: boolean;
defaultColumns: string[];
darkMode: boolean;
}
```
**Key UI Elements:**
- Toggle to enable/disable
- Dropdown for board templates
- Dropdown for default layout
- Slider for card width
- Toggles for feature flags
- Text area for default columns (comma-separated)
- Color picker for theme customization
## Best Practices
1. **Always save settings** - Call `await this.plugin.saveSettings()` after any change
2. **Use descriptive names** - Users should understand what each setting does
3. **Group related settings** - Use section headers (h3) to organize
4. **Provide defaults** - Always have sensible defaults in `DEFAULT_SETTINGS`
5. **Validate input** - Check user input before saving (trim, validate paths, etc.)
6. **Use conditionals wisely** - Show/hide advanced options based on basic settings
7. **Provide feedback** - Use `new Notice()` to confirm actions
8. **Redraw on enable/disable** - Call `this.display()` to update UI when features toggle
9. **Document complex settings** - Use `.setDesc()` liberally
10. **Test multi-step workflows** - Ensure settings changes work with all plugins enabled
## Testing Your Settings UI
```typescript
// In your test file
const plugin = new MyPlugin(app, manifest);
plugin.loadSettings();
plugin.registerSettingTab(new UnifiedSettingTab(app, plugin));
// Settings should load without errors
// Check that all toggles/inputs work
// Verify settings persist after reload
// Test conditional displays
```
---
This guide provides a foundation for building clean, organized, and user-friendly plugin settings. Adapt these patterns to your specific needs!