diff --git a/packages/@vuepress/core/lib/app/app.js b/packages/@vuepress/core/lib/app/app.js
index 70bc09683f..bcccec1797 100644
--- a/packages/@vuepress/core/lib/app/app.js
+++ b/packages/@vuepress/core/lib/app/app.js
@@ -16,6 +16,7 @@ import Content from './components/Content.js'
 import ContentSlotsDistributor from './components/ContentSlotsDistributor'
 import OutboundLink from './components/OutboundLink.vue'
 import ClientOnly from './components/ClientOnly'
+import TOC from './components/TOC.vue'
 
 // suggest dev server restart on base change
 if (module.hot) {
@@ -46,6 +47,8 @@ Vue.component('ClientOnly', ClientOnly)
 // core components
 Vue.component('Layout', getLayoutAsyncComponent('Layout'))
 Vue.component('NotFound', getLayoutAsyncComponent('NotFound'))
+// markdown components
+Vue.component('TOC', TOC)
 
 // global helper for adding base path to absolute urls
 Vue.prototype.$withBase = function (path) {
diff --git a/packages/@vuepress/core/lib/app/components/HeaderList.vue b/packages/@vuepress/core/lib/app/components/HeaderList.vue
new file mode 100644
index 0000000000..a1945e082c
--- /dev/null
+++ b/packages/@vuepress/core/lib/app/components/HeaderList.vue
@@ -0,0 +1,20 @@
+<template>
+  <component :is="listType[0]">
+    <li v-for="(item, index) in items" :key="index">
+      <router-link :to="'#' + item.slug" v-text="item.title" />
+      <HeaderList v-if="item.children" :items="item.children" :list-type="innerListType" />
+    </li>
+  </component>
+</template>
+
+<script>
+export default {
+  name: 'HeaderList',
+  props: ['items', 'listType'],
+  computed: {
+    innerListType () {
+      return this.listType.slice(Math.min(this.listType.length - 1, 1))
+    }
+  }
+}
+</script>
diff --git a/packages/@vuepress/core/lib/app/components/TOC.vue b/packages/@vuepress/core/lib/app/components/TOC.vue
new file mode 100644
index 0000000000..697e68c146
--- /dev/null
+++ b/packages/@vuepress/core/lib/app/components/TOC.vue
@@ -0,0 +1,57 @@
+<template>
+  <div>
+    <slot name="header" />
+    <HeaderList :items="groupedHeaders" :list-type="listTypes" />
+    <slot name="footer" />
+  </div>
+</template>
+
+<script>
+import HeaderList from './HeaderList.vue'
+export default {
+  props: {
+    listType: {
+      type: [String, Array],
+      default: 'ul'
+    },
+    includeLevel: {
+      type: Array,
+      default: () => [2, 3]
+    }
+  },
+  components: { HeaderList },
+  computed: {
+    listTypes () {
+      return typeof this.listType === 'string' ? [this.listType] : this.listType
+    },
+    groupedHeaders () {
+      return this.groupHeaders(this.$page.headers).list
+    }
+  },
+  methods: {
+    groupHeaders (headers, startLevel = 1) {
+      const list = []
+      let index = 0
+      while (index < headers.length) {
+        const header = headers[index]
+        if (header.level < startLevel) break
+        if (header.level > startLevel) {
+          const result = this.groupHeaders(headers.slice(index), header.level)
+          if (list.length) {
+            list[list.length - 1].children = result.list
+          } else {
+            list.push(...result.list)
+          }
+          index += result.index
+        } else {
+          if (header.level <= this.includeLevel[1] && header.level >= this.includeLevel[0]) {
+            list.push({ ...header })
+          }
+          index += 1
+        }
+      }
+      return { list, index }
+    }
+  }
+}
+</script>
diff --git a/packages/@vuepress/markdown/index.js b/packages/@vuepress/markdown/index.js
index cc8d09fb40..22172291b4 100644
--- a/packages/@vuepress/markdown/index.js
+++ b/packages/@vuepress/markdown/index.js
@@ -17,10 +17,10 @@ const convertRouterLinkPlugin = require('./lib/link')
 const containersPlugin = require('./lib/containers')
 const markdownSlotsContainersPlugin = require('./lib/markdownSlotsContainers')
 const snippetPlugin = require('./lib/snippet')
+const tocPlugin = require('./lib/tableOfContents')
 const emojiPlugin = require('markdown-it-emoji')
 const anchorPlugin = require('markdown-it-anchor')
-const tocPlugin = require('markdown-it-table-of-contents')
-const { parseHeaders, slugify: _slugify, logger, chalk, hash } = require('@vuepress/shared-utils')
+const { slugify: _slugify, logger, chalk, hash } = require('@vuepress/shared-utils')
 
 /**
  * Create markdown by config.
@@ -97,11 +97,7 @@ module.exports = (markdown = {}) => {
       .end()
 
     .plugin(PLUGINS.TOC)
-      .use(tocPlugin, [Object.assign({
-        slugify,
-        includeLevel: [2, 3],
-        format: parseHeaders
-      }, toc)])
+      .use(tocPlugin, [toc])
       .end()
 
   if (lineNumbers) {
diff --git a/packages/@vuepress/markdown/lib/tableOfContents.js b/packages/@vuepress/markdown/lib/tableOfContents.js
new file mode 100644
index 0000000000..5e32161ec7
--- /dev/null
+++ b/packages/@vuepress/markdown/lib/tableOfContents.js
@@ -0,0 +1,83 @@
+// reference: https://github.com/Oktavilla/markdown-it-table-of-contents
+
+const defaults = {
+  includeLevel: [2, 3],
+  containerClass: 'table-of-contents',
+  markerPattern: /^\[\[toc\]\]/im,
+  listType: 'ul',
+  containerHeaderHtml: '',
+  containerFooterHtml: ''
+}
+
+module.exports = (md, options) => {
+  options = Object.assign({}, defaults, options)
+  const tocRegexp = options.markerPattern
+
+  function toc (state, silent) {
+    var token
+    var match
+
+    // Reject if the token does not start with [
+    if (state.src.charCodeAt(state.pos) !== 0x5B /* [ */) {
+      return false
+    }
+    // Don't run any pairs in validation mode
+    if (silent) {
+      return false
+    }
+
+    // Detect TOC markdown
+    match = tocRegexp.exec(state.src)
+    match = !match ? [] : match.filter(function (m) { return m })
+    if (match.length < 1) {
+      return false
+    }
+
+    // Build content
+    token = state.push('toc_open', 'toc', 1)
+    token.markup = '[[toc]]'
+    token = state.push('toc_body', '', 0)
+    token = state.push('toc_close', 'toc', -1)
+
+    // Update pos so the parser can continue
+    var newline = state.src.indexOf('\n')
+    if (newline !== -1) {
+      state.pos = state.pos + newline
+    } else {
+      state.pos = state.pos + state.posMax + 1
+    }
+
+    return true
+  }
+
+  md.renderer.rules.toc_open = function () {
+    return vBindEscape`<TOC
+      :class=${options.containerClass}
+      :list-type=${options.listType}
+      :include-level=${options.includeLevel}
+    >`
+  }
+
+  md.renderer.rules.toc_body = function () {
+    return `<template slot="header">${options.containerHeaderHtml}</template>`
+      + `<template slot="footer">${options.containerFooterHtml}</template>`
+  }
+
+  md.renderer.rules.toc_close = function () {
+    return `</TOC>`
+  }
+
+  // Insert TOC
+  md.inline.ruler.after('emphasis', 'toc', toc)
+}
+
+/** escape double quotes in v-bind derivatives */
+function vBindEscape (strs, ...args) {
+  return strs.reduce((prev, curr, index) => {
+    return prev + curr + (index >= args.length
+      ? ''
+      : `"${JSON.stringify(args[index])
+        .replace(/"/g, "'")
+        .replace(/([^\\])(\\\\)*\\'/g, (_, char) => char + '\\u0022')}"`)
+  }, '')
+}
diff --git a/packages/@vuepress/markdown/package.json b/packages/@vuepress/markdown/package.json
index 196639d6ba..2fc0cf6d84 100644
--- a/packages/@vuepress/markdown/package.json
+++ b/packages/@vuepress/markdown/package.json
@@ -26,7 +26,6 @@
     "markdown-it-chain": "^1.3.0",
     "markdown-it-container": "^2.0.0",
     "markdown-it-emoji": "^1.4.0",
-    "markdown-it-table-of-contents": "^0.4.0",
     "prismjs": "^1.13.0"
   },
   "author": "Evan You",
diff --git a/packages/docs/docs/config/README.md b/packages/docs/docs/config/README.md
index 19f394de65..914531940b 100644
--- a/packages/docs/docs/config/README.md
+++ b/packages/docs/docs/config/README.md
@@ -220,9 +220,17 @@ The key and value pair will be added to `<a>` tags that point to an external lin
 ### markdown.toc
 
 - Type: `Object`
-- Default: `{ includeLevel: [2, 3] }`
 
-Options for [markdown-it-table-of-contents](https://github.com/Oktavilla/markdown-it-table-of-contents). (Note: prefer `markdown.slugify` if you want to customize header ids.)
+This attribute will control the behaviour of `[[TOC]]`. It contains the following options:
+
+- includeLevel: [number, number], level of headers to be included, defaults to `[2, 3]`.
+- containerClass: string, the class name for the container, defaults to `table-of-contents`.
+- markerPattern: RegExp, the regular expression for the marker to be replaced with TOC, defaults to `/^\[\[toc\]\]/im`.
+- listType: string or Array, labels for all levels of the list, defaults to `"ul"`.
+- containerHeaderHtml: string, an HTML string for container header, defaults to `""`.
+- containerFooterHtml: string, an HTML string for container footer, defaults to `""`.
+
+We also provide a [global component TOC](../guide/using-vue.md#toc) which allows for more free control by passing props directly to `<TOC>`.
 
 ### markdown.extendMarkdown
 
diff --git a/packages/docs/docs/guide/markdown.md b/packages/docs/docs/guide/markdown.md
index 8631f91531..868046ae4a 100644
--- a/packages/docs/docs/guide/markdown.md
+++ b/packages/docs/docs/guide/markdown.md
@@ -108,15 +108,21 @@ A list of all emojis available can be found [here](https://github.com/markdown-i
 
 **Input**
 
-```
+```md
 [[toc]]
 ```
 
+or
+
+```md
+<TOC/>
+```
+
 **Output**
 
 [[toc]]
 
-Rendering of TOC can be configured using the [`markdown.toc`](../config/README.md#markdown-toc) option.
+Rendering of TOC can be configured using the [`markdown.toc`](../config/README.md#markdown-toc) option, or as props of [TOC component](./using-vue.md#toc), like `<TOC list-type="ol" :include-level="[2, Infinity]"/>`.
 
 ## Custom Containers
 
diff --git a/packages/docs/docs/guide/using-vue.md b/packages/docs/docs/guide/using-vue.md
index f5628b6a3a..52d46eb898 100644
--- a/packages/docs/docs/guide/using-vue.md
+++ b/packages/docs/docs/guide/using-vue.md
@@ -224,6 +224,27 @@ Specify a specific slot for a specific page (.md) for rendering. This will be ve
 - [Markdown Slot](./markdown-slot.md)
 - [Writing a theme > Content Outlet](../theme/writing-a-theme.md#content-outlet)
 
+### TOC <Badge text="1.0.0-alpha.41+"/>
+
+- **Props**:
+  - `listType` - string or Array, defaults to `"ul"`
+  - `includeLevel` - [number, number], defaults to `[2, 3]`
+
+- **Slots**: `header`, `footer`
+
+- **Usage**:
+
+You can add a custom table of contents by specify some props to this component. `includeLevel` decides which level of headers should be included. `listType` decides the tags of lists. If specified as an array, the component will take the first element as the first-level list type and so on. If there are not enough values provided, the last value will be used for all the remaining list types.
+
+``` md
+<TOC :list-type="['ol', 'ul']">
+  <p slot="header"><strong>Custom Table of Contents</strong></p>
+</TOC>
+```
+
+<TOC :list-type="['ol', 'ul']">
+  <p slot="header"><strong>Custom Table of Contents</strong></p>
+</TOC>
 
 ### Badge <Badge text="beta" type="warn"/> <Badge text="0.10.1+"/> <Badge text="default theme"/>
 
diff --git a/packages/docs/docs/zh/config/README.md b/packages/docs/docs/zh/config/README.md
index c5042d356a..21e1bc17c7 100644
--- a/packages/docs/docs/zh/config/README.md
+++ b/packages/docs/docs/zh/config/README.md
@@ -212,9 +212,17 @@ VuePress 提供了一种添加额外样式的简便方法。你可以创建一
 ### markdown.toc
 
 - 类型: `Object`
-- 默认值: `{ includeLevel: [2, 3] }`
 
-[markdown-it-table-of-contents](https://github.com/Oktavilla/markdown-it-table-of-contents) 的选项。
+这个值将会控制 `[[TOC]]` 默认行为。它包含下面的选项:
+
+- includeLevel: [number, number],决定哪些级别的标题会被显示在目录中,默认值为 `[2, 3]`。
+- containerClass: string,决定了目录容器的类名,默认值为 `table-of-contents`。
+- markerPattern: RegExp,决定了标题匹配的正则表达式,默认值为 `/^\[\[toc\]\]/im`。
+- listType: string 或 Array,决定了各级列表的标签,默认值为 `"ul"`。
+- containerHeaderHtml: string,在目录开头插入的 HTML 字符串,默认值为 `""`。
+- containerFooterHtml: string,在目录结尾插入的 HTML 字符串,默认值为 `""`。
+
+此外,我们还提供了[全局组件 TOC](../guide/using-vue.md#toc),可以通过直接向 `<TOC>` 传递属性实现更加自由的控制。
 
 ### markdown.extendMarkdown
 
diff --git a/packages/docs/docs/zh/guide/markdown.md b/packages/docs/docs/zh/guide/markdown.md
index e44611335a..db05999fd6 100644
--- a/packages/docs/docs/zh/guide/markdown.md
+++ b/packages/docs/docs/zh/guide/markdown.md
@@ -106,15 +106,21 @@ lang: en-US
 
 **Input**
 
-```
+```md
 [[toc]]
 ```
 
+或者
+
+```md
+<TOC/>
+```
+
 **Output**
 
 [[toc]]
 
-目录(Table of Contents)的渲染可以通过  [`markdown.toc`](../config/README.md#markdown-toc) 选项来配置。
+目录(Table of Contents)的渲染可以通过  [`markdown.toc`](../config/README.md#markdown-toc) 选项来配置,也可以在 [TOC 组件](./using-vue.md#toc)中直接传入,如 `<TOC list-type="ol" :include-level="[2, Infinity]"/>`。
 
 ## 自定义容器
 
diff --git a/packages/docs/docs/zh/guide/using-vue.md b/packages/docs/docs/zh/guide/using-vue.md
index cf6d8b2f22..57b758c3f2 100644
--- a/packages/docs/docs/zh/guide/using-vue.md
+++ b/packages/docs/docs/zh/guide/using-vue.md
@@ -225,6 +225,27 @@ export default {
 - [Markdown 插槽](./markdown-slot.md)
 - [开发主题 > 获取渲染内容](../theme/writing-a-theme.md#获取渲染内容)
 
+### TOC <Badge text="1.0.0-alpha.41+"/>
+
+- **Props**:
+  - `listType` - string 或 Array, 默认值为 `"ul"`
+  - `includeLevel` - [number, number], 默认值为 `[2, 3]`
+
+- **Slots**: `header`, `footer`
+
+- **Usage**:
+
+你可以通过一些属性来实现一个自定义的目录。`includeLevel` 决定了哪些级别的标题会被显示在目录中。`listType` 决定了所有列表的标签。如果设置为了数组,组件将会使用第一个元素作为第一级列表的标签,以此类推。如果提供的标签不够多,将使用提供的最后一个值作为全部剩下的列表标签。
+
+``` md
+<TOC :list-type="['ol', 'ul']">
+  <p slot="header"><strong>自定义目录</strong></p>
+</TOC>
+```
+
+<TOC :list-type="['ol', 'ul']">
+  <p slot="header"><strong>自定义目录</strong></p>
+</TOC>
 
 ### Badge <Badge text="beta" type="warn"/> <Badge text="0.10.1+"/> <Badge text="默认主题"/>