diff --git a/config.json b/config.json
index 9acaf807aa1c485346cf1749283d2d77ccfab50b..c4505815c1fce8492a9028b01bc47019be9e4dc0 100644
--- a/config.json
+++ b/config.json
@@ -8,7 +8,11 @@
 		"homepage_slug": "index",
 		"static_dir": "theme",
 		"css_file": "main.css",
-		"default_template": "page.html"
+		"default_template": "page.html",
+		"link_target": {
+			"internal": "_self",
+			"external": "_blank"
+		}
 	},
 	"default_status": "published",
 	"toc_depth": 3,
diff --git a/theme/static/css/main.css b/theme/static/css/main.css
index 1114568ad820e14ef0f3133738a3780a17b49aab..b55860e89ab874ea1708e02165049c4b40ce1a72 100644
--- a/theme/static/css/main.css
+++ b/theme/static/css/main.css
@@ -58,6 +58,20 @@ a {
 a:hover {
 	text-decoration: underline;
 }
+a.external::after {
+	/* content: '↪'; */
+	content: '🡕';
+	/*background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20.092'%3E%3Cpath d='m12 0 2.561 2.537-6.975 6.976 2.828 2.828 6.988-6.988L20 7.927 19.998 0H12z'/%3E%3Cpath d='M9 4.092v-2H0v18h18v-9h-2v7H2v-14h7z'/%3E%3C/svg%3E");
+	background-repeat: no-repeat no-repeat;
+	background-position: center center;
+	background-size: cover;*/
+}
+a.external[href^="mailto:"]::after {
+	content: '';
+}
+a.external[href^="mailto:"]::before {
+	content: '📧';
+}
 
 p, blockquote, ul, ol, dl, table, pre {
 	margin: 0 0 1em 0;
diff --git a/theme/templates/base.html b/theme/templates/base.html
index b798077b55fe0fcfad98567359fb547ebcef5f63..5f51c79999630c110bf9148bc3914936e026158b 100644
--- a/theme/templates/base.html
+++ b/theme/templates/base.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
 
 {%- import 'macros/getters.html' as get with context -%}
+{%- import 'macros/link.html' as link with context -%}
 {%- import 'macros/cards.html' as cards with context -%}
 {%- import 'macros/renderers.html' as render with context -%}
 {%- import 'macros/content_renderer.html' as content_renderer with context -%}
+{%- import 'macros/nav.html' as nav with context -%}
 
 <html lang="{%- block html_lang -%}{{ l }}{%- endblock html_lang -%}">
 <head>
@@ -39,28 +41,7 @@
 		<label for="show-header-menu" class="show-header-menu">&#9776;</label>
 		<input type="checkbox" id="show-header-menu" role="button">
 		<label for="show-header-menu" class="show-header-menu-bg"> </label>
-		<nav>
-			<ul>
-			{% for item in config.menuitems -%}
-				{%- if item.tag is defined -%}
-					{%- call(tagtitle, tagcolor, tagurl, tagpage, tagpages) get.tag_by_name(item.tag, l) -%}
-						<li style="--category-color: {{ tagcolor }}"><a href="{{ siteurl }}/{{ tagurl }}">{{ tagtitle|e }}</a></li>
-					{%- endcall -%}
-				{%- elif item.slug is defined -%}
-					{%- call(page) get.page_by_slug(item.slug, l) -%}
-					{%- call(cattitle, catcolor, caturl, catpage, catpages) get.tag_by_name(page.category, l) -%}
-						<li style="--category-color: {{ catcolor }}"><a href="{{ siteurl }}/{{ page.url }}">{{ page.title|e }}</a></li>
-					{%- endcall -%}
-					{%- endcall -%}
-				{%- else -%}
-					<br />
-					<strong>ERROR: menuitems: Cannot parse item: {{ item|string|e }}</strong><br />
-					<br />
-				{%- endif -%}
-				{#- TODO add active class if this is the current site -#}
-			{%- endfor %}
-			</ul>
-		</nav>
+		{{ nav.render_menu(config.menuitems, l) }}
 	</div>
 	<main>
 	{% block content %}
diff --git a/theme/templates/macros/cards.html b/theme/templates/macros/cards.html
index 13f78d5c954d54c4a04c8847fb419da4fac7dfc1..a3eb588f284ff1028b2edeafc3d85225ea7bc420 100644
--- a/theme/templates/macros/cards.html
+++ b/theme/templates/macros/cards.html
@@ -9,13 +9,13 @@
 	</ul>
 {%- endmacro -%}
 
-{%- macro card(title, url, catcolor, is_url_external = True) -%}
-	<li style="--category-color: {{ catcolor }}"><a href="{% if not is_url_external %}{{ siteurl }}/{% endif %}{{ url }}">{{ title|e }}</a></li>
+{%- macro card(title, url, catcolor, lang) -%}
+	<li style="--category-color: {{ catcolor }}">{{ link.render(url, title, lang) }}</li>
 {%- endmacro -%}
 
 {%- macro card_from_page(page, lang) -%}
-	{%- call(cattitle, catcolor, caturl, catpage, catpages) get.tag_by_name(page.category, l) -%}
-		{{ card(title=page.title, url=page.url, catcolor=catcolor, is_url_external = False) }}
+	{%- call(cattitle, catcolor, caturl, catpage, catpages) get.tag_by_name(page.category, lang) -%}
+		{{ card(title=page.title, url=['slug',page.slug,page.lang]|join(':'), catcolor=catcolor, lang=lang) }}
 	{%- endcall -%}
 {%- endmacro -%}
 
@@ -25,7 +25,7 @@
 	{%- endif -%}
 	{%- for page in pages -%}
 		{%- if max == None or loop.index < max -%}
-			{{ cards.card_from_page(page) }}
+			{{ cards.card_from_page(page, lang) }}
 		{%- endif -%}
 	{%- endfor -%}
 	{%- if standalone -%}
diff --git a/theme/templates/macros/content_renderer.html b/theme/templates/macros/content_renderer.html
index 610e05f96bdbda1872189542e470edf2a7d15e48..affdfe572c60d6b02fc77b177af782f83c9b373b 100644
--- a/theme/templates/macros/content_renderer.html
+++ b/theme/templates/macros/content_renderer.html
@@ -22,7 +22,7 @@
 	{%- set attr_extra = attr['extra']|d({}) -%}
 
 	{%- if id %} id="{{ id }}"{%- endif -%}
-	{%- if classes|length > 0 %} class="{{ classes|join(' ') }}"{%- endif -%}
+	{%- if classes|length %} class="{{ classes|join(' ') }}"{%- endif -%}
 	{%- for key, value in attr_extra.items() -%}
 		{%- if key not in extra -%}
 			{{ render_attr_extra(key, value, lang) }}
@@ -45,16 +45,6 @@
 	{%- endif -%}
 {%- endmacro -%}
 
-{%- macro render_link(url, content, lang, attr = None, title = None) -%}
-	<a {{ render_attr(attr, lang, extra={"href": url, "title": {"value":title, "escape": true}}) }}>
-		{%- if content is string -%}
-			{{ content|e }}
-		{%- else -%}
-			{{ render_blocks_or_inlines(content, lang) }}
-		{%- endif -%}
-	</a>
-{%- endmacro -%}
-
 {%- macro render_image(url, alt, lang, attr = None, title = None) -%}
 	<img {{ render_attr(attr, lang, extra={"src": url, "title": {"value":title, "escape": true}}) }} alt="
 		{%- if alt is string -%}
@@ -322,7 +312,7 @@
 	{%- set content = inline['content'] -%}
 	{%- set url = inline['url'] -%}
 	{%- set title = inline['title'] -%}
-	{{ render_link(url, content, lang, attr, title) }}
+	{{ link.render(url, content, lang, attr, title) }}
 {%- endmacro -%}
 
 {%- macro render_inline_image(inline, lang) -%}
diff --git a/theme/templates/macros/link.html b/theme/templates/macros/link.html
new file mode 100644
index 0000000000000000000000000000000000000000..9526dcb666d7098830aed3f4c2ac251a52cd1fb0
--- /dev/null
+++ b/theme/templates/macros/link.html
@@ -0,0 +1,77 @@
+{%- import 'macros/content_renderer.html' as content_renderer with context -%}
+{%- import 'macros/getters.html' as get with context -%}
+
+{%- macro render(url, content, lang, attr = None, title = None) -%}
+	{%- call(parsedurl, anchor, reflang, is_external, reftype, refid, refpage, tagcattitle, tagcatcolor) parse_url(url, lang) -%}
+		{%- set ns = namespace(relation=None, fullurl=parsedurl) -%}
+		{%- if anchor -%}
+			{%- set ns.fullurl = [parsedurl, anchor]|join('#') -%}
+		{%- endif -%}
+		{%- if is_external -%}
+			{%- set ns.relation = "external" -%}
+		{%- else -%}
+			{%- set ns.relation = "internal" -%}
+		{%- endif -%}
+		{%- set target = config.theme.link_target[ns.relation] -%}
+		<a {{ content_renderer.render_attr(attr, lang, extra_classes=[ns.relation], extra={"href": ns.fullurl, "title": {"value":title, "escape": true}, "target": target}) }}>
+			{%- if content is string or content is none -%}
+				{%- if content is string and content|length -%}
+					{{ content|e }}
+				{%- else -%}
+					{%- if reftype == "tag" -%}
+						{{ tagcattitle|e }}
+					{%- else -%}
+						{{ refpage.title|e }}
+					{%- endif -%}
+				{%- endif -%}
+			{%- else -%}
+				{{ content_renderer.render_blocks_or_inlines(content, lang) }}
+			{%- endif -%}
+		</a>
+	{%- endcall -%}
+{%- endmacro -%}
+
+
+{#- returns: (url, anchor, reflang, is_external, reftype, refid, refpage, tagcattitle, tagcatcolor)-#}
+{%- macro parse_url(rawurl, lang) -%}
+	{%- set urlsplit = rawurl.split('#') -%}
+	{%- set anchor = urlsplit[1]|d(None) -%}
+	{%- set urlwoa = urlsplit[0] -%} {#- url without anchor -#}
+	{%- if not urlwoa|length -%}
+		{{- caller("", anchor, lang, False, None, None, None, None, None) -}}
+	{%- else -%}
+
+		{%- set components = urlwoa.split(':') -%}
+
+		{%- if components|length == 1 -%}
+			{%- set url = ['https://', urlwoa]|join('') -%}
+			{{- caller(url, anchor, lang, True, None, None, None, None, None) -}}
+		{%- else -%}
+			{%- set reftype = components[0] -%}
+			{%- set refid = components[1] -%}
+			{%- set reflang = components[2]|d(lang) -%}
+			{%- set ns = namespace(url=None, page=None, tagtitle=None, tagcolor=None, tagpage=None, catcolor=None, cattitle=None) -%}
+			{%- if reftype == "slug" -%}
+				{%- call(page) get.page_by_slug(refid, reflang) -%}
+					{%- set ns.url = [siteurl, page.url]|join('/') -%}
+					{%- set ns.page = page -%}
+					{%- call(cattitle, catcolor, caturl, catpage, catpages) get.tag_by_name(page.category, reflang) -%}
+						{%- set ns.cattitle = cattitle -%}
+						{%- set ns.catcolor = catcolor -%}
+					{%- endcall -%}
+				{%- endcall -%}
+				{{- caller(ns.url, anchor, reflang, False, reftype, refid, ns.page, ns.cattitle, ns.catcolor) -}}
+			{%- elif reftype == "tag" -%}
+				{%- call(tagtitle, tagcolor, tagurl, tagpage, tagpages) get.tag_by_name(refid, reflang) -%}
+					{%- set ns.url = [siteurl, tagurl]|join('/') -%}
+					{%- set ns.tagtitle = tagtitle -%}
+					{%- set ns.tagcolor = tagcolor -%}
+					{%- set ns.tagpage = tagpage -%}
+				{%- endcall -%}
+				{{- caller(ns.url, anchor, reflang, False, reftype, refid, ns.tagpage, ns.tagtitle, ns.tagcolor) -}}
+			{%- else -%}
+				{{- caller(urlwoa, anchor, reflang, True, None, None, None, None, None) -}}
+			{%- endif -%}
+		{%- endif -%}
+	{%- endif -%}
+{%- endmacro -%}
diff --git a/theme/templates/macros/nav.html b/theme/templates/macros/nav.html
new file mode 100644
index 0000000000000000000000000000000000000000..93c77dfefe76f3708e2ab66167eaa508e2e6205a
--- /dev/null
+++ b/theme/templates/macros/nav.html
@@ -0,0 +1,24 @@
+{%- import 'macros/link.html' as link with context -%}
+
+{%- macro render_menu(items, lang) -%}
+	<nav>
+		<ul>
+		{% for item in items -%}
+			{{ render_menu_item(item, lang) }}
+		{%- endfor %}
+		</ul>
+	</nav>
+{%- endmacro -%}
+
+{%- macro render_menu_item(item, lang) -%}
+	{%- if item is string -%}
+		{%- set url = item -%}
+		{%- call(parsedurl, anchor, reflang, is_external, reftype, refid, refpage, tagcattitle, tagcatcolor) link.parse_url(url, lang) -%}
+			<li style="--category-color: {{ tagcatcolor }}">
+				{{ link.render(url, None, lang) }}
+			</li>
+		{%- endcall -%}
+	{%- else -%}
+		TODO: handle custom menu items
+	{%- endif -%}
+{%- endmacro -%}
diff --git a/theme/templates/macros/renderers.html b/theme/templates/macros/renderers.html
index ea366c2b6072efb80d2262eeecd331ea3cf0d74b..6b7a62c64e6a3defc5b5f62809b9b8f2e78c4758 100644
--- a/theme/templates/macros/renderers.html
+++ b/theme/templates/macros/renderers.html
@@ -9,7 +9,7 @@
 {%- macro section_custom(s, lang) -%}
 	{{ cards.open() }}
 	{%- for c in s.content -%}
-		{{ cards.card(title=c[lang], url=c.url, catcolor=c.color) }}
+		{{ cards.card(title=c[lang], url=c.url, catcolor=c.color, lang=lang) }}
 	{%- endfor -%}
 	{{ cards.close() }}
 {%- endmacro -%}