diff --git a/docs/helpers.md b/docs/helpers.md
index 6bd8a069d..910846345 100644
--- a/docs/helpers.md
+++ b/docs/helpers.md
@@ -127,6 +127,12 @@ Only when you set both the `routerMode: 'history'` and `externalLinkTarget: '_se
 ### Hello, world! :id=hello-world
 ```
 
+## Customise the title for the sidebar items :sidebar="Customise title for sidebar"
+
+```md
+### How would I write a "hello, world" example? :sidebar="Hello, world?"
+```
+
 ## Markdown in html tag
 
 You need to insert a space between the html and markdown content.
diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js
index 255c49642..c59f5a1c9 100644
--- a/src/core/render/compiler.js
+++ b/src/core/render/compiler.js
@@ -207,34 +207,32 @@ export class Compiler {
      */
     origin.heading = renderer.heading = function (text, level) {
       let { str, config } = getAndRemoveConfig(text);
-      const nextToc = { level, title: removeAtag(str) };
+      const nextToc = { level };
 
       if (/<!-- {docsify-ignore} -->/g.test(str)) {
         str = str.replace('<!-- {docsify-ignore} -->', '');
-        nextToc.title = removeAtag(str);
         nextToc.ignoreSubHeading = true;
       }
 
       if (/{docsify-ignore}/g.test(str)) {
         str = str.replace('{docsify-ignore}', '');
-        nextToc.title = removeAtag(str);
         nextToc.ignoreSubHeading = true;
       }
 
       if (/<!-- {docsify-ignore-all} -->/g.test(str)) {
         str = str.replace('<!-- {docsify-ignore-all} -->', '');
-        nextToc.title = removeAtag(str);
         nextToc.ignoreAllSubs = true;
       }
 
       if (/{docsify-ignore-all}/g.test(str)) {
         str = str.replace('{docsify-ignore-all}', '');
-        nextToc.title = removeAtag(str);
         nextToc.ignoreAllSubs = true;
       }
 
       const slug = slugify(config.id || str);
       const url = router.toURL(router.getCurrentPath(), { id: slug });
+      nextToc.title = removeAtag(str);
+      nextToc.text = config.sidebar || nextToc.title;
       nextToc.slug = url;
       _self.toc.push(nextToc);
 
diff --git a/src/core/render/compiler/headline.js b/src/core/render/compiler/headline.js
index 61e4b3fb9..bbe9be19a 100644
--- a/src/core/render/compiler/headline.js
+++ b/src/core/render/compiler/headline.js
@@ -4,34 +4,32 @@ import { slugify } from './slugify';
 export const headingCompiler = ({ renderer, router, _self }) =>
   (renderer.code = (text, level) => {
     let { str, config } = getAndRemoveConfig(text);
-    const nextToc = { level, title: removeAtag(str) };
+    const nextToc = { level };
 
     if (/<!-- {docsify-ignore} -->/g.test(str)) {
       str = str.replace('<!-- {docsify-ignore} -->', '');
-      nextToc.title = removeAtag(str);
       nextToc.ignoreSubHeading = true;
     }
 
     if (/{docsify-ignore}/g.test(str)) {
       str = str.replace('{docsify-ignore}', '');
-      nextToc.title = removeAtag(str);
       nextToc.ignoreSubHeading = true;
     }
 
     if (/<!-- {docsify-ignore-all} -->/g.test(str)) {
       str = str.replace('<!-- {docsify-ignore-all} -->', '');
-      nextToc.title = removeAtag(str);
       nextToc.ignoreAllSubs = true;
     }
 
     if (/{docsify-ignore-all}/g.test(str)) {
       str = str.replace('{docsify-ignore-all}', '');
-      nextToc.title = removeAtag(str);
       nextToc.ignoreAllSubs = true;
     }
 
     const slug = slugify(config.id || str);
     const url = router.toURL(router.getCurrentPath(), { id: slug });
+    nextToc.title = removeAtag(str);
+    nextToc.text = config.sidebar || nextToc.title;
     nextToc.slug = url;
     _self.toc.push(nextToc);
 
diff --git a/src/core/render/tpl.js b/src/core/render/tpl.js
index 47ceeab74..ff5668179 100644
--- a/src/core/render/tpl.js
+++ b/src/core/render/tpl.js
@@ -92,7 +92,7 @@ export function tree(toc, tpl = '<ul class="app-sub-sidebar">{inner}</ul>') {
   let innerHTML = '';
   toc.forEach(node => {
     const title = node.title.replace(/(<([^>]+)>)/g, '');
-    innerHTML += `<li><a class="section-link" href="${node.slug}" title="${title}">${node.title}</a></li>`;
+    innerHTML += `<li><a class="section-link" href="${node.slug}" title="${title}">${node.text}</a></li>`;
     if (node.children) {
       innerHTML += tree(node.children, tpl);
     }
diff --git a/src/core/render/utils.js b/src/core/render/utils.js
index 42fbfa078..beedeea54 100644
--- a/src/core/render/utils.js
+++ b/src/core/render/utils.js
@@ -23,16 +23,18 @@ export function getAndRemoveConfig(str = '') {
 
   if (str) {
     str = str
-      .replace(/^('|")/, '')
-      .replace(/('|")$/, '')
-      .replace(/(?:^|\s):([\w-]+:?)=?([\w-%]+)?/g, (m, key, value) => {
-        if (key.indexOf(':') === -1) {
-          config[key] = (value && value.replace(/&quot;/g, '')) || true;
-          return '';
-        }
+      .replace(
+        /(?:^|\s):([\w-]+:?)=?([\w-%]+|&quot;((?!&quot;).)*&quot;|[“”][^“”]*[“”])?/g, // Note: because the provided `str` argument has been html-escaped, with backslashes stripped, we cannot support escaped characters in quoted strings :-(
+        function (m, key, value) {
+          if (key.indexOf(':') === -1) {
+            config[key] = (value && value.replace(/&quot;|[“”]/g, '')) || true;
+            return '';
+          }
 
-        return m;
-      })
+          return m;
+        }
+      )
+      .replace(/^('|")|('|")$/g, '')
       .trim();
   }
 
diff --git a/test/unit/render-util.test.js b/test/unit/render-util.test.js
index 574e0a8ef..f4f1fc405 100644
--- a/test/unit/render-util.test.js
+++ b/test/unit/render-util.test.js
@@ -58,6 +58,19 @@ describe('core/render/utils', () => {
         str: `[filename](_media/example.md ":include")`,
       });
     });
+
+    test('parse config with quoted string arguments', () => {
+      const result = getAndRemoveConfig(
+        `[filename](_media/example.md ':include :foo=&quot;bar :baz test&quot;')`
+      );
+
+      expect(result).toMatchObject({
+        config: {
+          foo: 'bar :baz test',
+        },
+        str: `[filename](_media/example.md ':include')`,
+      });
+    });
   });
 });
 
@@ -68,22 +81,31 @@ describe('core/render/tpl', () => {
         level: 2,
         slug: '#/cover?id=basic-usage',
         title: '<span style="color:red">Basic usage</span>',
+        text: '<span style="color:red">Basic usage</span>',
       },
       {
         level: 2,
         slug: '#/cover?id=custom-background',
         title: 'Custom background',
+        text: 'Custom background',
       },
       {
         level: 2,
         slug: '#/cover?id=test',
         title:
           '<img src="/docs/_media/favicon.ico" data-origin="/_media/favicon.ico" alt="ico">Test',
+        text: '<img src="/docs/_media/favicon.ico" data-origin="/_media/favicon.ico" alt="ico">Test',
+      },
+      {
+        level: 2,
+        slug: '#/cover?id=different-title-and-label',
+        title: 'Long title string',
+        text: 'Short string',
       },
     ]);
 
     expect(result).toBe(
-      `<ul class="app-sub-sidebar"><li><a class="section-link" href="#/cover?id=basic-usage" title="Basic usage"><span style="color:red">Basic usage</span></a></li><li><a class="section-link" href="#/cover?id=custom-background" title="Custom background">Custom background</a></li><li><a class="section-link" href="#/cover?id=test" title="Test"><img src="/docs/_media/favicon.ico" data-origin="/_media/favicon.ico" alt="ico">Test</a></li></ul>`
+      `<ul class="app-sub-sidebar"><li><a class="section-link" href="#/cover?id=basic-usage" title="Basic usage"><span style="color:red">Basic usage</span></a></li><li><a class="section-link" href="#/cover?id=custom-background" title="Custom background">Custom background</a></li><li><a class="section-link" href="#/cover?id=test" title="Test"><img src="/docs/_media/favicon.ico" data-origin="/_media/favicon.ico" alt="ico">Test</a></li><li><a class="section-link" href="#/cover?id=different-title-and-label" title="Long title string">Short string</a></li></ul>`
     );
   });
 });