diff --git a/src/core/base/model.change.ts b/src/core/base/model.change.ts
index 5393b601..e8ee76de 100644
--- a/src/core/base/model.change.ts
+++ b/src/core/base/model.change.ts
@@ -1,12 +1,12 @@
 import {overrideEvent} from '@exadel/esl/modules/esl-utils/dom';
 
-import type {UIPPlugin} from './plugin';
 import type {UIPRoot} from './root';
 import type {UIPStateModel} from './model';
+import type {UIPSource} from './source';
 
 export type UIPChangeInfo = {
-  modifier: UIPPlugin | UIPRoot;
-  type: 'html' | 'js' | 'note';
+  modifier: object;
+  type: UIPSource;
   force?: boolean;
 };
 
@@ -38,7 +38,7 @@ export class UIPChangeEvent extends Event {
     return this.changes.filter((change) => change.type === 'html');
   }
 
-  public isOnlyModifier(modifier: UIPPlugin | UIPRoot): boolean {
+  public isOnlyModifier(modifier: object): boolean {
     return this.changes.every((change) => change.modifier === modifier);
   }
 }
diff --git a/src/core/base/model.ts b/src/core/base/model.ts
index 7161b656..1501edaf 100644
--- a/src/core/base/model.ts
+++ b/src/core/base/model.ts
@@ -10,10 +10,9 @@ import {
 
 import {UIPSnippetItem} from './snippet';
 
-import type {UIPRoot} from './root';
-import type {UIPPlugin} from './plugin';
 import type {UIPSnippetTemplate} from './snippet';
 import type {UIPChangeInfo} from './model.change';
+import type {UIPEditableSource} from './source';
 
 /** Type for function to change attribute's current value */
 export type TransformSignature = (
@@ -27,7 +26,7 @@ export type ChangeAttrConfig = {
   /** Attribute to change */
   attribute: string;
   /** Changes initiator */
-  modifier: UIPPlugin | UIPRoot;
+  modifier: object;
 } & ({
   /** New {@link attribute} value */
   value: string | boolean;
@@ -63,20 +62,24 @@ export class UIPStateModel extends SyntheticEventTarget {
    * @param js - new state
    * @param modifier - plugin, that initiates the change
    */
-  public setJS(js: string, modifier: UIPPlugin | UIPRoot): void {
-    const script = UIPJSNormalizationPreprocessors.preprocess(js);
+  public setJS(js: string, modifier: object): void {
+    const script = this.normalizeJS(js);
     if (this._js === script) return;
     this._js = script;
     this._changes.push({modifier, type: 'js', force: true});
     this.dispatchChange();
   }
 
+  protected normalizeJS(snippet: string): string {
+    return UIPJSNormalizationPreprocessors.preprocess(snippet);
+  }
+
   /**
    * Sets current note state to the passed one
    * @param text - new state
    * @param modifier - plugin, that initiates the change
    */
-  public setNote(text: string, modifier: UIPPlugin | UIPRoot): void {
+  public setNote(text: string, modifier: object): void {
     const note = UIPNoteNormalizationPreprocessors.preprocess(text);
     if (this._note === note) return;
     this._note = note;
@@ -90,24 +93,51 @@ export class UIPStateModel extends SyntheticEventTarget {
    * @param modifier - plugin, that initiates the change
    * @param force - marker, that indicates if html changes require iframe rerender
    */
-  public setHtml(markup: string, modifier: UIPPlugin | UIPRoot, force: boolean = false): void {
-    const html = UIPHTMLNormalizationPreprocessors.preprocess(markup);
+  public setHtml(markup: string, modifier: object, force: boolean = false): void {
+    const root = this.normalizeHTML(markup);
+    if (root.innerHTML.trim() === this.html.trim()) return;
+    this._html = root;
+    this._changes.push({modifier, type: 'html', force});
+    this.dispatchChange();
+  }
+
+  protected normalizeHTML(snippet: string): HTMLElement {
+    const html = UIPHTMLNormalizationPreprocessors.preprocess(snippet);
     const {head, body: root} = new DOMParser().parseFromString(html, 'text/html');
 
     Array.from(head.children).reverse().forEach((el) => {
-      if (el.tagName === 'STYLE') {
-        root.innerHTML = '\n' + root.innerHTML;
-        root.insertBefore(el, root.firstChild);
-      }
+      if (el.tagName !== 'STYLE') return;
+      root.innerHTML = '\n' + root.innerHTML;
+      root.insertBefore(el, root.firstChild);
     });
 
-    if (root.innerHTML.trim() !== this.html.trim()) {
-      this._html = root;
-      this._changes.push({modifier, type: 'html', force});
-      this.dispatchChange();
-    }
+    return root;
+  }
+
+  public isHTMLChanged(): boolean {
+    if (!this.activeSnippet) return false;
+    return this.normalizeHTML(this.activeSnippet.html).innerHTML.trim() !== this.html.trim();
   }
 
+  public isJSChanged(): boolean {
+    if (!this.activeSnippet) return false;
+    return this.normalizeJS(this.activeSnippet.js) !== this.js;
+  }
+
+  public reset(source: UIPEditableSource, modifier: object): void {
+    if (source === 'html') this.resetHTML(modifier);
+    if (source === 'js') this.resetJS(modifier);
+  }
+
+  protected resetJS(modifier: object): void {
+    if (this.activeSnippet) this.setJS(this.activeSnippet.js, modifier);
+  }
+
+  protected resetHTML(modifier: object): void {
+    if (this.activeSnippet) this.setHtml(this.activeSnippet.html, modifier);
+  }
+
+
   /** Current js state getter */
   public get js(): string {
     return this._js;
@@ -150,7 +180,7 @@ export class UIPStateModel extends SyntheticEventTarget {
   /** Changes current active snippet */
   public applySnippet(
     snippet: UIPSnippetItem,
-    modifier: UIPPlugin | UIPRoot
+    modifier: object
   ): void {
     if (!snippet) return;
     this._snippets.forEach((s) => (s.active = s === snippet));
@@ -162,7 +192,7 @@ export class UIPStateModel extends SyntheticEventTarget {
     );
   }
   /** Applies an active snippet from DOM */
-  public applyCurrentSnippet(modifier: UIPPlugin | UIPRoot): void {
+  public applyCurrentSnippet(modifier: object): void {
     const activeSnippet = this.anchorSnippet || this.activeSnippet || this.snippets[0];
     this.applySnippet(activeSnippet, modifier);
   }
diff --git a/src/core/base/root.ts b/src/core/base/root.ts
index da0da2fd..8c353a7b 100644
--- a/src/core/base/root.ts
+++ b/src/core/base/root.ts
@@ -3,11 +3,13 @@ import {
   memoize,
   boolAttr,
   listen,
-  prop
+  prop,
+  attr
 } from '@exadel/esl/modules/esl-utils/decorators';
 
 import {UIPStateModel} from './model';
 import {UIPChangeEvent} from './model.change';
+import {UIPStateStorage} from './state.storage';
 
 import type {UIPSnippetTemplate} from './snippet';
 import type {UIPChangeInfo} from './model.change';
@@ -36,6 +38,10 @@ export class UIPRoot extends ESLBaseElement {
 
   /** Indicates that the UIP components' theme is dark */
   @boolAttr() public darkTheme: boolean;
+  /** Key to store UIP state in the local storage */
+  @attr({defaultValue: ''}) public storeKey: string;
+  /** State storage based on `storeKey` */
+  public storage: UIPStateStorage | undefined;
 
   /** Indicates ready state of the uip-root */
   @boolAttr({readonly: true}) public ready: boolean;
@@ -51,7 +57,7 @@ export class UIPRoot extends ESLBaseElement {
     return Array.from(this.querySelectorAll(UIPRoot.SNIPPET_SEL));
   }
 
-  protected delyedScrollIntoView(): void {
+  protected delayedScrollIntoView(): void {
     setTimeout(() => {
       this.scrollIntoView({behavior: 'smooth', block: 'start'});
     }, 100);
@@ -59,13 +65,15 @@ export class UIPRoot extends ESLBaseElement {
 
   protected override connectedCallback(): void {
     super.connectedCallback();
+    if (this.storeKey) this.storage = new UIPStateStorage(this.storeKey, this.model);
+
     this.model.snippets = this.$snippets;
     this.model.applyCurrentSnippet(this);
     this.$$attr('ready', true);
     this.$$fire(this.READY_EVENT, {bubbles: false});
 
     if (this.model.anchorSnippet) {
-      this.delyedScrollIntoView();
+      this.delayedScrollIntoView();
     }
   }
 
diff --git a/src/core/base/source.ts b/src/core/base/source.ts
new file mode 100644
index 00000000..313ae571
--- /dev/null
+++ b/src/core/base/source.ts
@@ -0,0 +1,3 @@
+export type UIPEditableSource = 'js' | 'html';
+
+export type UIPSource = UIPEditableSource | 'note';
diff --git a/src/core/base/state.storage.ts b/src/core/base/state.storage.ts
new file mode 100644
index 00000000..245492be
--- /dev/null
+++ b/src/core/base/state.storage.ts
@@ -0,0 +1,91 @@
+import {ESLEventUtils} from '@exadel/esl/modules/esl-utils/dom';
+import {listen} from '@exadel/esl/modules/esl-utils/decorators';
+
+import type {UIPStateModel} from './model';
+import type {UIPEditableSource} from './source';
+
+interface UIPStateStorageEntry {
+  ts: string;
+  snippets: string;
+}
+
+interface UIPStateModelSnippets {
+  js: string;
+  html: string;
+  note: string;
+}
+
+export class UIPStateStorage {
+  public static readonly STORAGE_KEY = 'uip-editor-storage';
+
+  protected static readonly EXPIRATION_TIME = 3600000 * 12; // 12 hours
+
+  public constructor(protected storeKey: string, protected model: UIPStateModel) {
+    ESLEventUtils.subscribe(this);
+  }
+
+  protected loadEntry(key: string): string | null {
+    const entry = (this._lsState[key] || {}) as UIPStateStorageEntry;
+    if (parseInt(entry?.ts, 10) + UIPStateStorage.EXPIRATION_TIME > Date.now()) return entry.snippets || null;
+    this.removeEntry(key);
+    return null;
+  }
+
+  protected saveEntry(key: string, value: string): void {
+    this._lsState = Object.assign(this._lsState, {[key]: {ts: Date.now(), snippets: value}});
+  }
+
+  protected removeEntry(key: string): void {
+    const data = this._lsState;
+    delete this._lsState[key];
+    this._lsState = data;
+  }
+
+  protected get _lsState(): Record<string, any> {
+    return JSON.parse(localStorage.getItem(UIPStateStorage.STORAGE_KEY) || '{}');
+  }
+  
+  protected set _lsState(value: Record<string, any>) {
+    localStorage.setItem(UIPStateStorage.STORAGE_KEY, JSON.stringify(value));
+  }
+
+  protected getStateKey(): string | null {
+    const {activeSnippet} = this.model;
+    if (!activeSnippet || !this.storeKey) return null;
+    return JSON.stringify({key: this.storeKey, snippet: activeSnippet.html});
+  }
+
+  public loadState(): void {
+    const stateKey = this.getStateKey();
+    const state = stateKey && this.loadEntry(stateKey);
+    if (!state) return; 
+    
+    const stateobj = JSON.parse(state) as UIPStateModelSnippets;
+    this.model.setHtml(stateobj.html, this, true);
+    this.model.setJS(stateobj.js, this);
+    this.model.setNote(stateobj.note, this);
+  }
+
+  public saveState(): void {
+    const stateKey = this.getStateKey();
+    const {js, html, note} = this.model;
+    stateKey && this.saveEntry(stateKey, JSON.stringify({js, html, note}));
+  }
+
+  public resetState(source: UIPEditableSource): void {
+    const stateKey = this.getStateKey();
+    stateKey && this.removeEntry(stateKey);
+
+    this.model.reset(source, this);
+  }
+
+  @listen({event: 'uip:model:change', target: ($this: UIPStateStorage) => $this.model})
+  protected _onModelChange(): void {
+    this.saveState()
+  }
+
+  @listen({event: 'uip:model:snippet:change', target: ($this: UIPStateStorage) => $this.model})
+  protected _onSnippetChange(): void {
+    this.loadState()
+  }
+}
diff --git a/src/plugins/copy/copy-button.shape.ts b/src/plugins/copy/copy-button.shape.ts
index 6f947e46..55648d9f 100644
--- a/src/plugins/copy/copy-button.shape.ts
+++ b/src/plugins/copy/copy-button.shape.ts
@@ -1,8 +1,9 @@
 import type {ESLBaseElementShape} from '@exadel/esl/modules/esl-base-element/core';
 import type {UIPCopy} from './copy-button';
+import type {UIPEditableSource} from '../../core/base/source';
 
 export interface UIPCopyShape extends ESLBaseElementShape<UIPCopy> {
-  source?: 'javascript' | 'js' | 'html';
+  source?: UIPEditableSource;
   children?: any;
 }
 
diff --git a/src/plugins/copy/copy-button.ts b/src/plugins/copy/copy-button.ts
index 5eca7d47..7de06749 100644
--- a/src/plugins/copy/copy-button.ts
+++ b/src/plugins/copy/copy-button.ts
@@ -4,6 +4,7 @@ import {attr} from '@exadel/esl/modules/esl-utils/decorators';
 import {UIPPluginButton} from '../../core/button/plugin-button';
 
 import type {ESLAlertActionParams} from '@exadel/esl/modules/esl-alert/core';
+import type {UIPEditableSource} from '../../core/base/source';
 
 /** Button-plugin to copy snippet to clipboard */
 export class UIPCopy extends UIPPluginButton {
@@ -11,7 +12,7 @@ export class UIPCopy extends UIPPluginButton {
   public static override defaultTitle = 'Copy to clipboard';
 
   /** Source type to copy (html | js) */
-  @attr({defaultValue: 'html'}) public source: string;
+  @attr({defaultValue: 'html'}) public source: UIPEditableSource;
 
   public static msgConfig: ESLAlertActionParams = {
     text: 'Playground content copied to clipboard',
@@ -20,14 +21,7 @@ export class UIPCopy extends UIPPluginButton {
 
   /** Content to copy */
   protected get content(): string | undefined {
-    switch (this.source) {
-      case 'js':
-      case 'javascript':
-        return this.model?.js;
-      case 'html':
-      default:
-        return this.model?.html;
-    }
+    if (this.source === 'js' || this.source === 'html') return this.model?.[this.source];
   }
 
   protected override connectedCallback(): void {
diff --git a/src/plugins/editor/editor.less b/src/plugins/editor/editor.less
index 8a31acfe..890baf04 100644
--- a/src/plugins/editor/editor.less
+++ b/src/plugins/editor/editor.less
@@ -9,7 +9,7 @@
     padding: 1em;
   }
 
-  &-header-copy {
+  &-header-copy, &-header-reset {
     position: relative;
     width: 25px;
     height: 25px;
diff --git a/src/plugins/editor/editor.tsx b/src/plugins/editor/editor.tsx
index 49e19396..4a31144f 100644
--- a/src/plugins/editor/editor.tsx
+++ b/src/plugins/editor/editor.tsx
@@ -12,11 +12,12 @@ import {attr, boolAttr, decorate, listen, memoize} from '@exadel/esl/modules/esl
 
 import {UIPPluginPanel} from '../../core/panel/plugin-panel';
 import {CopyIcon} from '../copy/copy-button.icon';
-
+import {ResetIcon} from '../reset/reset-button.icon';
 import {EditorIcon} from './editor.icon';
 
 import type {UIPSnippetsList} from '../snippets-list/snippets-list';
 import type {UIPChangeEvent} from '../../core/base/model.change';
+import type {UIPEditableSource} from '../../core/base/source';
 
 /**
  * Editor {@link UIPPlugin} custom element definition
@@ -30,7 +31,7 @@ export class UIPEditor extends UIPPluginPanel {
   public static highlight = (editor: HTMLElement): void => Prism.highlightElement(editor, false);
 
   /** Source for Editor plugin (default: 'html') */
-  @attr({defaultValue: 'html'}) public source: 'js' | 'javascript' | 'html';
+  @attr({defaultValue: 'html'}) public source: UIPEditableSource;
 
   /** Marker to display copy widget */
   @boolAttr({name: 'copy'}) public showCopy: boolean;
@@ -45,6 +46,7 @@ export class UIPEditor extends UIPPluginPanel {
     return (
       <div className={type.is + '-toolbar uip-plugin-header-toolbar'}>
         {this.showCopy ? <uip-copy class={type.is + '-header-copy'} source={this.source}><CopyIcon/></uip-copy> : ''}
+        {this.$root?.storeKey ? <uip-reset class={type.is + '-header-reset'} source={this.source}><ResetIcon/></uip-reset> : ''}
       </div>
     ) as HTMLElement;
   }
@@ -142,30 +144,19 @@ export class UIPEditor extends UIPPluginPanel {
   @decorate(debounce, 2000)
   protected _onChange(): void {
     if (!this.editable) return;
-    switch (this.source) {
-      case 'js':
-      case 'javascript':
-        this.model!.setJS(this.value, this);
-        break;
-      case 'html':
-        this.model!.setHtml(this.value, this);
-    }
+    if (this.source === 'js') this.model!.setJS(this.value, this);
+    if (this.source === 'html') this.model!.setHtml(this.value, this);
   }
 
   /** Change editor's markup from markup state changes */
   @listen({event: 'uip:change', target: ($this: UIPEditor) => $this.$root})
   protected _onRootStateChange(e?: UIPChangeEvent): void {
     if (e && e.isOnlyModifier(this)) return;
-    switch (this.source) {
-      case 'js':
-      case 'javascript':
-        if (e && !e.jsChanges.length) return;
-        this.value = this.model!.js;
-        break;
-      case 'html':
-        if (e && !e.htmlChanges.length) return;
-        this.value = this.model!.html;
-    }
+
+    if (
+      (this.source === 'js' && (!e || e.jsChanges.length)) ||
+      (this.source === 'html' && (!e || e.htmlChanges.length))
+    ) this.value = this.model![this.source];
   }
 
   /** Handles snippet change to set readonly value */
diff --git a/src/plugins/registration.less b/src/plugins/registration.less
index d085d27a..10de06e4 100644
--- a/src/plugins/registration.less
+++ b/src/plugins/registration.less
@@ -8,5 +8,6 @@
 @import './settings/settings.less';
 
 @import './copy/copy-button.less';
+@import './reset/reset-button.less';
 @import './theme/theme-toggle.less';
 @import './direction/dir-toggle.less';
diff --git a/src/plugins/registration.ts b/src/plugins/registration.ts
index 09045cfd..ee02c6f8 100644
--- a/src/plugins/registration.ts
+++ b/src/plugins/registration.ts
@@ -17,6 +17,9 @@ export {UIPSetting, UIPSettings, UIPTextSetting, UIPBoolSetting, UIPSelectSettin
 import {UIPCopy} from './copy/copy-button';
 export {UIPCopy};
 
+import {UIPReset} from './reset/reset-button';
+export {UIPReset};
+
 import {UIPNote} from './note/note';
 export {UIPNote};
 
@@ -34,6 +37,7 @@ export const registerSettings = (): void => {
 
 export const registerPlugins = (): void => {
   UIPCopy.register();
+  UIPReset.register();
   UIPDirSwitcher.register();
   UIPThemeSwitcher.register();
 
diff --git a/src/plugins/reset/reset-button.icon.tsx b/src/plugins/reset/reset-button.icon.tsx
new file mode 100644
index 00000000..b467cbe7
--- /dev/null
+++ b/src/plugins/reset/reset-button.icon.tsx
@@ -0,0 +1,7 @@
+import React from 'jsx-dom';
+
+export const ResetIcon = (): SVGElement => (
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 12 12">
+  <path d="M4 0C1.8 0 0 1.8 0 4s1.8 4 4 4c1.1 0 2.12-.43 2.84-1.16l-.72-.72c-.54.54-1.29.88-2.13.88-1.66 0-3-1.34-3-3s1.34-3 3-3c.83 0 1.55.36 2.09.91L4.99 3h3V0L6.8 1.19C6.08.47 5.09 0 3.99 0z"/>
+</svg>
+) as SVGElement;
diff --git a/src/plugins/reset/reset-button.less b/src/plugins/reset/reset-button.less
new file mode 100644
index 00000000..fd9b6f94
--- /dev/null
+++ b/src/plugins/reset/reset-button.less
@@ -0,0 +1,14 @@
+.uip-reset {
+  display: inline-flex;
+  cursor: pointer;
+
+  > svg {
+    fill: currentColor;
+    width: 100%;
+    height: 100%;
+  }
+
+  &[disabled] {
+    display: none;
+  }
+}
diff --git a/src/plugins/reset/reset-button.shape.ts b/src/plugins/reset/reset-button.shape.ts
new file mode 100644
index 00000000..ac487996
--- /dev/null
+++ b/src/plugins/reset/reset-button.shape.ts
@@ -0,0 +1,16 @@
+import type {ESLBaseElementShape} from '@exadel/esl/modules/esl-base-element/core';
+import type {UIPReset} from './reset-button';
+import type {UIPEditableSource} from '../../core/base/source';
+
+export interface UIPResetShape extends ESLBaseElementShape<UIPReset> {
+  source?: UIPEditableSource;
+  children?: any;
+}
+
+declare global {
+  namespace JSX {
+    interface IntrinsicElements {
+      'uip-reset': UIPResetShape;
+    }
+  }
+}
diff --git a/src/plugins/reset/reset-button.ts b/src/plugins/reset/reset-button.ts
new file mode 100644
index 00000000..d3c00cc7
--- /dev/null
+++ b/src/plugins/reset/reset-button.ts
@@ -0,0 +1,29 @@
+import './reset-button.shape';
+
+import {listen, attr, boolAttr} from '@exadel/esl/modules/esl-utils/decorators';
+
+import {UIPPluginButton} from '../../core/button/plugin-button';
+import {UIPRoot} from '../../core/base/root';
+
+import type {UIPEditableSource} from '../../core/base/source';
+
+/** Button-plugin to reset snippet to default settings */
+export class UIPReset extends UIPPluginButton {
+  public static override is = 'uip-reset';
+
+  @boolAttr() public disabled: boolean;
+
+  /** Source type to copy (html | js) */
+  @attr({defaultValue: 'html'}) public source: UIPEditableSource;
+
+  public override onAction(): void {
+    this.$root?.storage!.resetState(this.source);
+  }
+
+  @listen({event: 'uip:model:change', target: ($this: UIPRoot) => $this.model})
+  protected _onModelChange(): void {
+    if (!this.model || !this.model.activeSnippet) return;
+    if (this.source === 'js')  this.disabled = !this.model.isJSChanged();
+    if (this.source === 'html') this.disabled = !this.model.isHTMLChanged();
+  }
+}