diff --git a/src/common/text/elements/link.js b/src/common/text/elements/link.js
new file mode 100644
index 0000000000000000000000000000000000000000..03315bb2c04ed8bfbf8ea04a08cfc700a73e830c
--- /dev/null
+++ b/src/common/text/elements/link.js
@@ -0,0 +1,19 @@
+/**
+ * Link
+ */
+
+module.exports = text => {
+	const match = text.match(/^\??\[(.+?)\]\((https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.=\+\-]+)\)/);
+	if (!match) return null;
+	const silent = text[0] == '?';
+	const link = match[0];
+	const title = match[1];
+	const url = match[2];
+	return {
+		type: 'link',
+		content: link,
+		title: title,
+		url: url,
+		silent: silent
+	};
+};
diff --git a/src/common/text/elements/url.js b/src/common/text/elements/url.js
index f350b707acb84b4209f85638008277ed139c8e05..1003aff9c3bc6bec3806f97415aa6e6724310223 100644
--- a/src/common/text/elements/url.js
+++ b/src/common/text/elements/url.js
@@ -3,11 +3,12 @@
  */
 
 module.exports = text => {
-	const match = text.match(/^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.=\+\-]+/); 
+	const match = text.match(/^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.=\+\-]+/);
 	if (!match) return null;
-	const link = match[0];
+	const url = match[0];
 	return {
-		type: 'link',
-		content: link
+		type: 'url',
+		content: url,
+		url: url
 	};
 };
diff --git a/src/common/text/index.js b/src/common/text/index.js
index 44241690336a5f0f6f6c136b5403a0609eb1d320..ab1342230c56c86abb0503678126f4ef2ba79e90 100644
--- a/src/common/text/index.js
+++ b/src/common/text/index.js
@@ -5,6 +5,7 @@
 const elements = [
 	require('./elements/bold'),
 	require('./elements/url'),
+	require('./elements/link'),
 	require('./elements/mention'),
 	require('./elements/hashtag'),
 	require('./elements/code'),
diff --git a/src/web/app/common/scripts/text-compiler.js b/src/web/app/common/scripts/text-compiler.js
index 2edffbb7664d780421ecd0fb45283f9f20500687..d4570ca923936f7f1d44aa6670b1d1f00fe1ded0 100644
--- a/src/web/app/common/scripts/text-compiler.js
+++ b/src/web/app/common/scripts/text-compiler.js
@@ -21,8 +21,10 @@ module.exports = (tokens, shouldBreak) => {
 					.replace(/(\r\n|\n|\r)/g, shouldBreak ? '<br>' : ' ');
 			case 'bold':
 				return '<strong>' + escape(token.bold) + '</strong>';
-			case 'link':
+			case 'url':
 				return '<mk-url href="' + escape(token.content) + '" target="_blank"></mk-url>';
+			case 'link':
+				return '<a class="link" href="' + escape(token.url) + '" target="_blank">' + escape(token.title) + '</a>';
 			case 'mention':
 				return '<a href="' + CONFIG.url + '/' + escape(token.username) + '" target="_blank" data-user-preview="' + token.content + '" ' + (me && me.username == token.username ? 'data-is-me' : '') + '>' + token.content + '</a>';
 			case 'hashtag': // TODO
diff --git a/src/web/app/desktop/tags/post-detail.tag b/src/web/app/desktop/tags/post-detail.tag
index 69379317fcd0a0b3b39332e3ada6d22afdc8e880..ca2d59f2e2c315d4b09c901037dd15d7f4518cfe 100644
--- a/src/web/app/desktop/tags/post-detail.tag
+++ b/src/web/app/desktop/tags/post-detail.tag
@@ -233,6 +233,16 @@
 							font-size 1.5em
 							color #717171
 
+							.link
+								&:after
+									content "\f14c"
+									display inline-block
+									padding-left 2px
+									font-family FontAwesome
+									font-size .9em
+									font-weight 400
+									font-style normal
+
 							> mk-url-preview
 								margin-top 8px
 
@@ -367,10 +377,10 @@
 
 					// URLをプレビュー
 					tokens
-					.filter(t => t.type == 'link')
+					.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
 					.map(t => {
 						riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), {
-							url: t.content
+							url: t.url
 						});
 					});
 				}
diff --git a/src/web/app/desktop/tags/timeline-post.tag b/src/web/app/desktop/tags/timeline-post.tag
index 9a1dede2dad6142c00fa7d6e8f6ac6eb08d0aacc..8929a8dd5ae5efc7d6d62d7b5e2be05cb40dcf43 100644
--- a/src/web/app/desktop/tags/timeline-post.tag
+++ b/src/web/app/desktop/tags/timeline-post.tag
@@ -229,6 +229,16 @@
 							mk-url-preview
 								margin-top 8px
 
+							.link
+								&:after
+									content "\f14c"
+									display inline-block
+									padding-left 2px
+									font-family FontAwesome
+									font-size .9em
+									font-weight 400
+									font-style normal
+
 							> .reply
 								margin-right 8px
 								color #717171
@@ -344,10 +354,10 @@
 
 				// URLをプレビュー
 				tokens
-				.filter(t => t.type == 'link')
+				.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
 				.map(t => {
 					riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), {
-						url: t.content
+						url: t.url
 					});
 				});
 			}
diff --git a/src/web/app/mobile/tags/post-detail.tag b/src/web/app/mobile/tags/post-detail.tag
index b32fe6173d654bbd15d67cbb6f8f968f9c717067..f6f6bb62dbb7665666a3617dcdc26f2bf8d12b01 100644
--- a/src/web/app/mobile/tags/post-detail.tag
+++ b/src/web/app/mobile/tags/post-detail.tag
@@ -230,6 +230,16 @@
 							@media (min-width 500px)
 								font-size 24px
 
+							.link
+								&:after
+									content "\f14c"
+									display inline-block
+									padding-left 2px
+									font-family FontAwesome
+									font-size .9em
+									font-weight 400
+									font-style normal
+
 							> mk-url-preview
 								margin-top 8px
 
@@ -368,10 +378,10 @@
 
 					// URLをプレビュー
 					tokens
-					.filter(t => t.type == 'link')
+					.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
 					.map(t => {
 						riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), {
-							url: t.content
+							url: t.url
 						});
 					});
 				}
diff --git a/src/web/app/mobile/tags/timeline-post.tag b/src/web/app/mobile/tags/timeline-post.tag
index 9da25b7b2f1dfb7576de2ae5fe97f2314712072d..f706dc7de64f395e1955ebe277a63d6a4438de99 100644
--- a/src/web/app/mobile/tags/timeline-post.tag
+++ b/src/web/app/mobile/tags/timeline-post.tag
@@ -209,6 +209,16 @@
 							> .dummy
 								display none
 
+							.link
+								&:after
+									content "\f14c"
+									display inline-block
+									padding-left 2px
+									font-family FontAwesome
+									font-size .9em
+									font-weight 400
+									font-style normal
+
 							mk-url-preview
 								margin-top 8px
 
@@ -318,10 +328,10 @@
 
 				// URLをプレビュー
 				tokens
-				.filter(t => t.type == 'link')
+				.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
 				.map(t => {
 					riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), {
-						url: t.content
+						url: t.url
 					});
 				});
 			}
diff --git a/test/text.js b/test/text.js
index e2527cfe0b097ba2786e4ca6632aeec8351e891e..dd4521c835927a235617fcf4552e9c747e8f4677 100644
--- a/test/text.js
+++ b/test/text.js
@@ -49,11 +49,23 @@ describe('Text', () => {
 			], tokens);
 		});
 
-		it('link', () => {
+		it('url', () => {
 			const tokens = analyze('https://himasaku.net');
-			assert.deepEqual([
-				{ type: 'link', content: 'https://himasaku.net' }
-			], tokens);
+			assert.deepEqual([{
+				type: 'url',
+				content: 'https://himasaku.net',
+				url: 'https://himasaku.net'
+			}], tokens);
+		});
+
+		it('link', () => {
+			const tokens = analyze('[ひまさく](https://himasaku.net)');
+			assert.deepEqual([{
+				type: 'link',
+				content: '[ひまさく](https://himasaku.net)',
+				title: 'ひまさく',
+				url: 'https://himasaku.net'
+			}], tokens);
 		});
 
 		it('emoji', () => {