Obsidian provides a rich set of UI components for building plugin interfaces. This guide covers the essential components for creating modals, notices, and settings.
Modal Component
Modals are popup dialogs that appear over the main interface. They’re perfect for forms, confirmations, and focused interactions.
Basic Modal
Extend the Modal class to create custom modals:
import { App , Modal } from 'obsidian' ;
export class ExampleModal extends Modal {
constructor ( app : App ) {
super ( app );
}
onOpen () {
const { contentEl } = this ;
contentEl . createEl ( 'h2' , { text: 'Example Modal' });
contentEl . createEl ( 'p' , { text: 'This is a modal dialog.' });
const button = contentEl . createEl ( 'button' , { text: 'Close' });
button . addEventListener ( 'click' , () => {
this . close ();
});
}
onClose () {
const { contentEl } = this ;
contentEl . empty ();
}
}
// Open the modal
const modal = new ExampleModal ( this . app );
modal . open ();
Modal with Title
Set the modal title using setTitle():
export class TitledModal extends Modal {
onOpen () {
this . setTitle ( 'Settings' );
this . contentEl . createEl ( 'p' , {
text: 'Configure your settings here.'
});
}
}
Modal Properties
Property Type Description appAppReference to the app instance scopeScopeScope for registering hotkeys containerElHTMLElementThe modal’s container element modalElHTMLElementThe modal content area titleElHTMLElementThe title element contentElHTMLElementThe main content element
Modal with Callback
Create modals that return values:
export class InputModal extends Modal {
result : string ;
onSubmit : ( result : string ) => void ;
constructor ( app : App , onSubmit : ( result : string ) => void ) {
super ( app );
this . onSubmit = onSubmit ;
}
onOpen () {
const { contentEl } = this ;
contentEl . createEl ( 'h3' , { text: 'Enter a value' });
const input = contentEl . createEl ( 'input' , {
type: 'text' ,
placeholder: 'Type here...'
});
input . addEventListener ( 'change' , ( e ) => {
this . result = ( e . target as HTMLInputElement ). value ;
});
const btnContainer = contentEl . createDiv ();
const submitBtn = btnContainer . createEl ( 'button' , { text: 'Submit' });
submitBtn . addEventListener ( 'click' , () => {
this . close ();
this . onSubmit ( this . result );
});
}
}
// Usage
new InputModal ( this . app , ( result ) => {
console . log ( 'User entered:' , result );
}). open ();
Use setCloseCallback() to handle cleanup when the modal closes.
SuggestModal
SuggestModal provides a searchable list of suggestions:
import { App , SuggestModal , TFile } from 'obsidian' ;
export class FileSuggestModal extends SuggestModal < TFile > {
getSuggestions ( query : string ) : TFile [] {
return this . app . vault . getMarkdownFiles ()
. filter ( file =>
file . basename . toLowerCase (). includes ( query . toLowerCase ())
);
}
renderSuggestion ( file : TFile , el : HTMLElement ) {
el . createEl ( 'div' , { text: file . basename });
el . createEl ( 'small' , { text: file . path });
}
onChooseSuggestion ( file : TFile , evt : MouseEvent | KeyboardEvent ) {
console . log ( 'Selected:' , file . path );
}
}
FuzzySuggestModal
For fuzzy search with match highlighting:
import { FuzzySuggestModal } from 'obsidian' ;
interface Command {
id : string ;
name : string ;
}
export class CommandModal extends FuzzySuggestModal < Command > {
getItems () : Command [] {
return [
{ id: 'cmd1' , name: 'Open settings' },
{ id: 'cmd2' , name: 'Create note' },
{ id: 'cmd3' , name: 'Toggle sidebar' }
];
}
getItemText ( item : Command ) : string {
return item . name ;
}
onChooseItem ( item : Command , evt : MouseEvent | KeyboardEvent ) {
console . log ( 'Chose:' , item . name );
}
}
Notice Component
Notices are temporary notifications that appear at the top of the screen.
Basic Notice
import { Notice } from 'obsidian' ;
// Simple notice (default 5 seconds)
new Notice ( 'Operation completed!' );
// Custom duration (10 seconds)
new Notice ( 'This stays longer' , 10000 );
// Permanent notice (until dismissed)
const notice = new Notice ( 'Click to dismiss' , 0 );
Notice with Custom Content
const notice = new Notice ( '' , 5000 );
notice . noticeEl . empty ();
const container = notice . noticeEl . createDiv ();
container . createEl ( 'strong' , { text: 'Success!' });
container . createEl ( 'br' );
container . createEl ( 'span' , { text: 'Your file has been saved.' });
Update Notice Content
const notice = new Notice ( 'Processing...' , 0 );
setTimeout (() => {
notice . setMessage ( 'Still processing...' );
}, 2000 );
setTimeout (() => {
notice . setMessage ( 'Complete!' );
setTimeout (() => notice . hide (), 1000 );
}, 5000 );
Set duration to 0 to keep the notice visible until manually dismissed.
Setting Component
The Setting class creates consistent setting rows:
Basic Setting
import { Setting } from 'obsidian' ;
const containerEl = document . createElement ( 'div' );
new Setting ( containerEl )
. setName ( 'Setting name' )
. setDesc ( 'Description of what this setting does' )
. addText ( text => text
. setPlaceholder ( 'Enter value' )
. setValue ( 'default' )
. onChange ( async ( value ) => {
console . log ( 'New value:' , value );
}));
Toggle Setting
new Setting ( containerEl )
. setName ( 'Enable feature' )
. setDesc ( 'Turn this feature on or off' )
. addToggle ( toggle => toggle
. setValue ( true )
. onChange ( async ( value ) => {
console . log ( 'Toggled:' , value );
}));
Dropdown Setting
new Setting ( containerEl )
. setName ( 'Choose option' )
. addDropdown ( dropdown => dropdown
. addOption ( 'option1' , 'Option 1' )
. addOption ( 'option2' , 'Option 2' )
. addOption ( 'option3' , 'Option 3' )
. setValue ( 'option1' )
. onChange ( async ( value ) => {
console . log ( 'Selected:' , value );
}));
Slider Setting
new Setting ( containerEl )
. setName ( 'Adjust value' )
. setDesc ( 'Use slider to set a value' )
. addSlider ( slider => slider
. setLimits ( 0 , 100 , 5 )
. setValue ( 50 )
. setDynamicTooltip ()
. onChange ( async ( value ) => {
console . log ( 'Value:' , value );
}));
new Setting ( containerEl )
. setName ( 'Dangerous action' )
. setDesc ( 'This cannot be undone' )
. addButton ( button => button
. setButtonText ( 'Delete' )
. setWarning ()
. onClick ( async () => {
console . log ( 'Button clicked' );
}));
Multiple Components
new Setting ( containerEl )
. setName ( 'Multiple inputs' )
. addText ( text => text . setPlaceholder ( 'First' ))
. addText ( text => text . setPlaceholder ( 'Second' ))
. addButton ( button => button
. setButtonText ( 'Apply' )
. setCta ());
PluginSettingTab
Create a settings tab for your plugin:
import { App , PluginSettingTab , Setting } from 'obsidian' ;
import ExamplePlugin from './main' ;
export class ExampleSettingTab extends PluginSettingTab {
plugin : ExamplePlugin ;
constructor ( app : App , plugin : ExamplePlugin ) {
super ( app , plugin );
this . plugin = plugin ;
}
display () : void {
const { containerEl } = this ;
containerEl . empty ();
containerEl . createEl ( 'h2' , { text: 'Settings' });
new Setting ( containerEl )
. setName ( 'API Key' )
. setDesc ( 'Enter your API key' )
. addText ( text => text
. setPlaceholder ( 'Enter key' )
. setValue ( this . plugin . settings . apiKey )
. onChange ( async ( value ) => {
this . plugin . settings . apiKey = value ;
await this . plugin . saveSettings ();
}));
new Setting ( containerEl )
. setName ( 'Auto-save' )
. setDesc ( 'Automatically save changes' )
. addToggle ( toggle => toggle
. setValue ( this . plugin . settings . autoSave )
. onChange ( async ( value ) => {
this . plugin . settings . autoSave = value ;
await this . plugin . saveSettings ();
}));
}
}
Register Settings Tab
export default class ExamplePlugin extends Plugin {
settings : ExampleSettings ;
async onload () {
await this . loadSettings ();
this . addSettingTab ( new ExampleSettingTab ( this . app , this ));
}
async loadSettings () {
this . settings = Object . assign ({}, DEFAULT_SETTINGS , await this . loadData ());
}
async saveSettings () {
await this . saveData ( this . settings );
}
}
Color Picker
import { ColorComponent } from 'obsidian' ;
new Setting ( containerEl )
. setName ( 'Theme color' )
. addColorPicker ( color => color
. setValue ( '#ff0000' )
. onChange ( async ( value ) => {
console . log ( 'Color:' , value );
}));
Progress Bar
import { ProgressBarComponent } from 'obsidian' ;
const setting = new Setting ( containerEl )
. setName ( 'Progress' );
setting . controlEl . createDiv ( '' , ( el ) => {
const progress = new ProgressBarComponent ( el );
progress . setValue ( 50 ); // 0-100
});
Create context menus:
import { Menu } from 'obsidian' ;
const menu = new Menu ();
menu . addItem (( item ) =>
item
. setTitle ( 'Copy' )
. setIcon ( 'copy' )
. onClick (() => {
console . log ( 'Copy clicked' );
})
);
menu . addItem (( item ) =>
item
. setTitle ( 'Delete' )
. setIcon ( 'trash' )
. setWarning ( true )
. onClick (() => {
console . log ( 'Delete clicked' );
})
);
menu . addSeparator ();
menu . addItem (( item ) =>
item
. setTitle ( 'Cancel' )
. onClick (() => {
menu . hide ();
})
);
// Show at mouse event
menu . showAtMouseEvent ( event );
Best Practices
Always clean up in onClose():
onClose () {
const { contentEl } = this ;
contentEl . empty ();
}
Structure content properly:
const section = contentEl . createDiv ({ cls: 'setting-section' });
section . createEl ( 'h3' , { text: 'Section Title' });
const form = section . createEl ( 'form' );
// Add form fields
Handle Keyboard Shortcuts
Register shortcuts in modals:
onOpen () {
this . scope . register ([ 'Ctrl' ], 'Enter' , () => {
this . submit ();
return false ;
});
this . scope . register ([], 'Escape' , () => {
this . close ();
return false ;
});
}
Settings and Commands Learn about commands and settings
Creating Views Build custom view interfaces