{"version":"https://jsonfeed.org/version/1.1","title":"Posts tagged \"HTML\" in Blogs","description":"Posts tagged with \"HTML\" in Blogs","home_page_url":"https://codingotaku.com/blogs/tags/HTML","feed_url":"https://codingotaku.com/blogs/tags/HTML/feed.json","items":[{"id":"https://codingotaku.com/blogs/the-quest-for-a-js-free-responsive-semantic-and-accessible-navbar","url":"https://codingotaku.com/blogs/the-quest-for-a-js-free-responsive-semantic-and-accessible-navbar","title":"The Quest for a JS-free, Responsive, Semantic, and Accessible Navbar","content_html":"<p>When trying to find a way to create a navbar, you will notice that there is no semantic way to make one with good UX in both mobile and desktop. The reason is simple, we want an element that is collapsed in mobile and expanded in desktop. Like what you see in my website. Because that’s what we usually see on most websites.</p>\n<p>The implementation for responsive navbars are usually one of the following — ordered by the number of times I see it on the internet</p>\n<h2 id=\"div-soup-with-a-front-end-javascript-framework\">div-soup with a front-end JavaScript framework</h2>\n<p>I notice this the most, and to be frank, it’s usually more accessible than most people think due because most use of <a href=\"https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-expanded\">aria-expanded</a> attribute and <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Element/role\">role</a> property.</p>\n<p>The cons are following:</p>\n<h3 id=\"bloat\">Bloat</h3>\n<p>I don’t mean it in just in file size, the page often takes a long-time load, especially when I am on mobile data. The types of sites that does this are typically restaurants and grocery websites.</p>\n<h3 id=\"buggy\">Buggy</h3>\n<p>It is buggy more often than not when resizing the window. I resize webpages a lot since I use a tiling window manager on my laptop.</p>\n<p>When on mobile, it is buggy, probably because the JavaScript takes a long time to load/run. I almost always need to wait a few seconds before it even notice that I tapped on something.</p>\n<p>At least I learnt some patients this way because I know better to not tap on again just because it’s not doing anything.</p>\n<h2 id=\"hidden-checkbox-hack\">Hidden Checkbox Hack</h2>\n<p>This one is a simple hack, <a href=\"https://dev.to/joxx/toggling-mobile-navigation-visibility-with-css-the-checkbox-hack-7ej\">and it works well</a>, but it’s usually not accessible.</p>\n<p>The idea is to create a label with an icon (typically hamburger menu), and hide the checkbox.\nThe checkbox can toggle if you press on the label, and CSS is used to style the sibling element when the checkbox is toggled.</p>\n<p>The styling is done in many ways, the best way I know is to position the navbar off-screen and move it into the view when the checkbox is toggled.</p>\n<p>From the link, this is the first example and it works well.</p>\n<pre><code class=\"language-css\">nav {\n    position: absolute;\n    top: 0;\n    left: -300px; /* width of the menu */\n    width: 300px;\n    height: 100vh;\n    transition: 0.3s;\n}\n\n#menuToggle:checked + label + nav {\n    left: 0;\n}\n</code></pre>\n<p>Moving the navbar on and off-screen is better than hiding it because screen readers can still navigate into the navbar without toggling anything.</p>\n<p>The only con I can think of is:</p>\n<h3 id=\"its-not-accessible\">It’s not Accessible</h3>\n<p>This is for multiple reasons.</p>\n<ol>\n<li>The Label would be just a CSS with three lines that does not describe what it is, i.e, an empty label, so it’s confusing to navigate into with assistive tech. Devs with more knowledge are now adding <a href=\"https://www.w3.org/WAI/WCAG21/Techniques/css/C7\">visually hidden text</a> or <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/content\">CSS alt texts for content</a>, or just adding a visible <q>Menu</q> label.</li>\n<li>Even if the checkbox is labelled, it wouldn’t announce that something has changed in the screen because CSS cannot set <code>aria-expanded</code>.</li>\n<li>Not everyone can tap on small buttons on the top corner, even the able personals.</li>\n</ol>\n<h2 id=\"semantic-details-tag\">Semantic Details Tag</h2>\n<p><code>HTML</code> has plenty of interactive elements, like <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog\">dialog</a>, <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/select\">select</a> elements, and <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/popover\">popover</a> attribute.</p>\n<p>The <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/details\">details</a> tag is one of the oldest one that works is most browsers.</p>\n<p>For example, this snippet produces an interactive disclosure element.</p>\n<pre><code class=\"language-html\">&lt;details&gt;\n&lt;summary&gt;Spoiler&lt;/summary&gt;\nThis is a pure HTML interactive element.\n&lt;/details&gt;\n</code></pre>\n<output>\n<details>\n<summary>Spoiler</summary>\nThis is a pure HTML interactive element.\n</details>\n</output>\nBut what if you want to show only one menu at a time? You can use the `name` attribute, though [it doesn't work](https://caniuse.com/mdn-html_elements_details_name) on the older browsers.\n<pre><code class=\"language-html\">&lt;details name=\"accordion-menu\"&gt;\n&lt;summary&gt;First interactive menu&lt;/summary&gt;\nThis is another pure HTML interactive element.\n&lt;/details&gt;\n\n&lt;details name=\"accordion-menu\"&gt;\n&lt;summary&gt;Second interactive menu&lt;/summary&gt;\nThis is yet another pure HTML interactive element.\n&lt;/details&gt;\n</code></pre>\n<output>\n<details name=\"accordion-menu\">\n<summary>First interactive menu</summary>\nThis is another pure HTML interactive element.\n</details>\n<details name=\"accordion-menu\">\n<summary>Second interactive menu</summary>\nThis is yet another pure HTML interactive element.\n</details>\n</output>\n<p>With a bit of CSS, you can make it a menu that can overlay elements below it like a typical menu.</p>\n<pre><code class=\"language-css\">/*\n* I will be using this website's styles instead of this style my demo because,\n* I want it to work in all themes I have, but this CSS will give you a working menu.\n* You should be able to make it pretty~\n*/\n\n.navbar-nav {\n    display: flex;\n    align-items: center;\n    gap: 1rem;\n    list-style: none;\n    margin: 0;\n    padding: 0;\n    flex-wrap: wrap;\n}\n\n.nav-dropdown {\n    position: relative;\n}\n\n.nav-dropdown summary {\n    list-style: none;\n    cursor: pointer;\n    user-select: none;\n    display: flex;\n    align-items: center;\n    gap: 1rem;\n}\n\n.nav-link:hover {\n    background-color: #23F;\n    color: #FF2;\n}\n\n.nav-dropdown .dropdown-menu {\n    list-style: none;\n}\n\n.dropdown-menu {\n    position: absolute;\n    top: 100%;\n    left: 0;\n    min-width: 220px;\n    background-color: #fff;\n    border: 2px solid #555;\n    border-radius: 10px;\n    padding: 1rem;\n    margin-top: 0;\n    z-index: 1000;\n}\n</code></pre>\n<pre><code class=\"language-html\">&lt;nav class=\"navbar-nav\"&gt;\n&lt;details class=\"nav-dropdown\" name=\"interactive-menu\"&gt;\n   &lt;summary class=\"nav-link\"&gt;Menu 1&lt;/summary&gt;\n   &lt;ul class=\"dropdown-menu\" role=\"menu\"&gt;\n       &lt;li&gt;Menu Item 1&lt;/li&gt;\n       &lt;li&gt;Menu Item 2&lt;/li&gt;\n   &lt;/ul&gt;\n&lt;/details&gt;\n&lt;details class=\"nav-dropdown\" name=\"interactive-menu\"&gt;\n   &lt;summary class=\"nav-link\"&gt;Menu 2&lt;/summary&gt;\n   &lt;ul class=\"dropdown-menu\" role=\"menu\"&gt;\n       &lt;li&gt;Menu Item 1&lt;/li&gt;\n       &lt;li&gt;Menu Item 2&lt;/li&gt;\n   &lt;/ul&gt;\n&lt;/details&gt;\n&lt;/nav&gt;\n</code></pre>\n<output>\n<nav class=\"navbar-nav\">\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\n   <summary class=\"nav-link\">Menu 1</summary>\n   <ul class=\"dropdown-menu\">\n       <li>Menu Item 1</li>\n       <li>Menu Item 2</li>\n   </ul>\n</details>\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\n   <summary class=\"nav-link\">Menu 2</summary>\n   <ul class=\"dropdown-menu\">\n       <li>Menu Item 1</li>\n       <li>Menu Item 2</li>\n   </ul>\n</details>\n</nav>\n</output>\n<p>Cool, huh? Now For mobile, we can just wrap the nav inside another <code>details</code> tag because HTML allows it:</p>\n<pre><code class=\"language-css\">&lt;details class=\"nav-dropdown\"&gt;\n&lt;summary class=\"nav-link\"&gt;Mobile Menu&lt;/summary&gt;\n&lt;nav class=\"navbar-nav mobile-nav\"&gt;\n&lt;details class=\"nav-dropdown\" name=\"interactive-menu\"&gt;\n   &lt;summary class=\"nav-link\"&gt;Menu 1&lt;/summary&gt;\n   &lt;ul class=\"dropdown-menu\" role=\"menu\"&gt;\n       &lt;li&gt;Menu Item 1&lt;/li&gt;\n       &lt;li&gt;Menu Item 2&lt;/li&gt;\n   &lt;/ul&gt;\n&lt;/details&gt;\n&lt;details class=\"nav-dropdown\" name=\"interactive-menu\"&gt;\n   &lt;summary class=\"nav-link\"&gt;Menu 2&lt;/summary&gt;\n   &lt;ul class=\"dropdown-menu\" role=\"menu\"&gt;\n       &lt;li&gt;Menu Item 1&lt;/li&gt;\n       &lt;li&gt;Menu Item 2&lt;/li&gt;\n   &lt;/ul&gt;\n&lt;/details&gt;\n&lt;/nav&gt;\n&lt;/details&gt;\n</code></pre>\n<output>\n<details class=\"nav-dropdown\">\n<summary class=\"nav-link\">Mobile Menu</summary>\n<nav class=\"navbar-nav mobile-nav\">\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\n   <summary class=\"nav-link\">Menu 1</summary>\n   <ul class=\"dropdown-menu\">\n       <li>Menu Item 1</li>\n       <li>Menu Item 2</li>\n   </ul>\n</details>\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\n   <summary class=\"nav-link\">Menu 2</summary>\n   <ul class=\"dropdown-menu\">\n       <li>Menu Item 1</li>\n       <li>Menu Item 2</li>\n   </ul>\n</details>\n</nav>\n</details>\n</output>\n<p>If you are not on a Mobile device, Resize this browser window or open this page in a mobile to see what the mobile menu will look like.</p>\n<h3 id=\"details-tag-menu-accessibility-issues\">Details tag menu accessibility issues</h3>\n<h4 id=\"it-still-needs-javascript\">It still needs JavaScript</h4>\n<p>You will notice that the menu stays open even if you click outside, that’s because the details tag doesn’t handle clicks outside it.</p>\n<p>The Navigation will be confusing for most users who are used to click outside or press the <kbd>Esc</kbd> key to close the menu.</p>\n<p>One way to not have this issue would be to not use details tag, and instead use <code>popover</code> or the <code>dialog</code> element I mentioned before. But they do not work in most browsers presently used (at the time of writing) because people don’t always update their browsers. So I am fine with that inconvenience. Just click the menu again to dismiss.</p>\n<p>We can handle the clicks with JavaScript.</p>\n<pre><code class=\"language-js\">window.addEventListener(\"click\", function (event) {\n  // if clicks are not on the menu button itself\n  if (!event.target.closest(\".nav-dropdown\")) {\n    // Hide all the menu if open.\n    Array.from(document.querySelectorAll('.nav-dropdown[open]')).forEach(\nmenu=&gt;menu.removeAttribute(\"open\"))\n  }\n});\n</code></pre>\n<p>That will work, but JavaScript is disabled on this site, so you can’t test it here.</p>\n<h4 id=\"needs-duplication-of-navbar\">Needs duplication of navbar</h4>\n<p>To switch between desktop and mobile navigation, if you have many items in the navbar, it is common practice to make the navbar itself a menu. Unfortunately, there is no way to show the contents of a detail tag when it is closed using any means. So the best possible way would be to create two navbars and show/hide them using media queries.</p>\n<p>You may ask why the duplication of navbar is an accessibility issue when we can hide it. Well, the eye candy CSS can be disabled for people who don’t need it. There can be network issues, or some plugin might have blocked it. Or your CSS file might be corrupted, or visitors might be using a text-only browser, the list goes on.</p>\n<h5 id=\"neat-hidden-attribute\">Neat hidden Attribute</h5>\n<p><strong>This section was added on 2026-01-20</strong></p>\n<p>One way to tackle hiding an element in all browsers is by using the <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/hidden\">hidden</a> global attribute. It can be <a href=\"https://html.spec.whatwg.org/multipage/interaction.html#the-hidden-attribute\">overwritten easily using CSS</a> by styling it with <code>display</code> CSS property with any value besides <code>none</code> (i.e <code>display:block</code> will show the element regardless of its <code>hidden</code> HTML attribute).</p>\n<p><a href=\"https://css-tricks.com/author/chriscoyier/\">Chris Coyier</a> wrote on CSS tricks that <q><a href=\"https://css-tricks.com/the-hidden-attribute-is-visibly-weak/\">The <code>hidden</code> Attribute is Visibly Weak </a></q>. But it is one of the best ways to optionally element when the browser does not load CSS or JavaScript.</p>\n<p>Unfortunately, even the feature-rich text-based browsers I tried does not treat the <code>hidden</code> attribute as a global one. If you are one of the developers who work on such browsers, please add support for it.</p>\n<h2 id=\"the-best-navbar\">The Best Navbar</h2>\n<p>The best JS-free, responsive, semantic, and accessible navbar is to not have a navbar. Yes, you read that correctly.</p>\n<p>Instead of a navbar, there are two ways I can think of:</p>\n<h3 id=\"make-it-a-navblock\">Make it a Navblock</h3>\n<p>A navblock is a list of links on the top (header), or bottom (footer), of the page. I have seen many government websites doing this. It’s mostly because they have too many pages and not everything can be listed without overwhelming the visitor. Instead, show all relevant links somewhere on the page.</p>\n<h3 id=\"write-a-sentence\">Write a sentence</h3>\n<p>For small blogs and indie sites, it might be better to just write a small paragraph with the things you do and add links to them. For example, in the home page I wrote the following:</p>\n<blockquote>\n<p>This is my personal website containing my <a href=\"/blogs\">blogs</a>, <a href=\"/projects\">projects</a>, <a href=\"/stories\">stories</a>, other things I do, and <a href=\"/contact-me\">ways to contact me</a>. It <a href=\"/am-i-indieweb-yet\">partially supports</a> the <a href=\"https://indieweb.org/\">IndieWeb</a> while keeping accessibility, minimalism, and your privacy in mind.</p>\n</blockquote>\n<p>Adding this in every page will accomplish two things:</p>\n<ol>\n<li>Some description about the author will be present in every page.</li>\n<li>Avoid navbar and free up some screen space.</li>\n</ol>\n<p>I am sure you have better ideas, let me know it in using webmention below, or just send an email :)</p>\n","content_text":"When trying to find a way to create a navbar, you will notice that there is no semantic way to make one with good UX in both mobile and desktop. The reason is simple, we want an element that is collapsed in mobile and expanded in desktop. Like what you see in my website. Because that's what we usually see on most websites.\r\n\r\nThe implementation for responsive navbars are usually one of the following — ordered by the number of times I see it on the internet\r\n\r\n## div-soup with a front-end JavaScript framework\r\n\r\nI notice this the most, and to be frank, it's usually more accessible than most people think due because most use of [aria-expanded](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-expanded) attribute and [role](https://developer.mozilla.org/en-US/docs/Web/API/Element/role) property.\r\n\r\nThe cons are following:\r\n\r\n### Bloat\r\n\r\nI don't mean it in just in file size, the page often takes a long-time load, especially when I am on mobile data. The types of sites that does this are typically restaurants and grocery websites.\r\n\r\n### Buggy\r\n\r\nIt is buggy more often than not when resizing the window. I resize webpages a lot since I use a tiling window manager on my laptop.\r\n\r\nWhen on mobile, it is buggy, probably because the JavaScript takes a long time to load/run. I almost always need to wait a few seconds before it even notice that I tapped on something.\r\n\r\nAt least I learnt some patients this way because I know better to not tap on again just because it's not doing anything.\r\n\r\n## Hidden Checkbox Hack\r\n\r\nThis one is a simple hack, [and it works well](https://dev.to/joxx/toggling-mobile-navigation-visibility-with-css-the-checkbox-hack-7ej), but it's usually not accessible.\r\n\r\nThe idea is to create a label with an icon (typically hamburger menu), and hide the checkbox.\r\nThe checkbox can toggle if you press on the label, and CSS is used to style the sibling element when the checkbox is toggled. \r\n\r\nThe styling is done in many ways, the best way I know is to position the navbar off-screen and move it into the view when the checkbox is toggled.\r\n\r\nFrom the link, this is the first example and it works well.\r\n\r\n```css\r\nnav {\r\n    position: absolute;\r\n    top: 0;\r\n    left: -300px; /* width of the menu */\r\n    width: 300px;\r\n    height: 100vh;\r\n    transition: 0.3s;\r\n}\r\n\r\n#menuToggle:checked + label + nav {\r\n    left: 0;\r\n}\r\n```\r\n\r\nMoving the navbar on and off-screen is better than hiding it because screen readers can still navigate into the navbar without toggling anything.\r\n\r\nThe only con I can think of is:\r\n\r\n### It's not Accessible\r\n\r\nThis is for multiple reasons.\r\n\r\n1. The Label would be just a CSS with three lines that does not describe what it is, i.e, an empty label, so it's confusing to navigate into with assistive tech. Devs with more knowledge are now adding [visually hidden text](https://www.w3.org/WAI/WCAG21/Techniques/css/C7) or [CSS alt texts for content](https://developer.mozilla.org/en-US/docs/Web/CSS/content), or just adding a visible <q>Menu</q> label. \r\n2. Even if the checkbox is labelled, it wouldn't announce that something has changed in the screen because CSS cannot set `aria-expanded`.\r\n3. Not everyone can tap on small buttons on the top corner, even the able personals.\r\n\r\n## Semantic Details Tag\r\n\r\n`HTML` has plenty of interactive elements, like [dialog](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog), [select](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/select) elements, and [popover](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/popover) attribute.\r\n\r\nThe [details](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/details) tag is one of the oldest one that works is most browsers.\r\n\r\nFor example, this snippet produces an interactive disclosure element.\r\n\r\n```html\r\n<details>\r\n<summary>Spoiler</summary>\r\nThis is a pure HTML interactive element.\r\n</details>\r\n```\r\n<output>\r\n<details>\r\n<summary>Spoiler</summary>\r\nThis is a pure HTML interactive element.\r\n</details>\r\n</output>\r\nBut what if you want to show only one menu at a time? You can use the `name` attribute, though [it doesn't work](https://caniuse.com/mdn-html_elements_details_name) on the older browsers.\r\n\r\n```html\r\n<details name=\"accordion-menu\">\r\n<summary>First interactive menu</summary>\r\nThis is another pure HTML interactive element.\r\n</details>\r\n\r\n<details name=\"accordion-menu\">\r\n<summary>Second interactive menu</summary>\r\nThis is yet another pure HTML interactive element.\r\n</details>\r\n```\r\n<output>\r\n<details name=\"accordion-menu\">\r\n<summary>First interactive menu</summary>\r\nThis is another pure HTML interactive element.\r\n</details>\r\n\r\n<details name=\"accordion-menu\">\r\n<summary>Second interactive menu</summary>\r\nThis is yet another pure HTML interactive element.\r\n</details>\r\n</output>\r\n\r\nWith a bit of CSS, you can make it a menu that can overlay elements below it like a typical menu.\r\n\r\n```css\r\n/*\r\n* I will be using this website's styles instead of this style my demo because,\r\n* I want it to work in all themes I have, but this CSS will give you a working menu.\r\n* You should be able to make it pretty~\r\n*/\r\n\r\n.navbar-nav {\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 1rem;\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n    flex-wrap: wrap;\r\n}\r\n\r\n.nav-dropdown {\r\n    position: relative;\r\n}\r\n\r\n.nav-dropdown summary {\r\n    list-style: none;\r\n    cursor: pointer;\r\n    user-select: none;\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 1rem;\r\n}\r\n\r\n.nav-link:hover {\r\n    background-color: #23F;\r\n    color: #FF2;\r\n}\r\n\r\n.nav-dropdown .dropdown-menu {\r\n    list-style: none;\r\n}\r\n\r\n.dropdown-menu {\r\n    position: absolute;\r\n    top: 100%;\r\n    left: 0;\r\n    min-width: 220px;\r\n    background-color: #fff;\r\n    border: 2px solid #555;\r\n    border-radius: 10px;\r\n    padding: 1rem;\r\n    margin-top: 0;\r\n    z-index: 1000;\r\n}\r\n```\r\n\r\n```html\r\n<nav class=\"navbar-nav\">\r\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\r\n   <summary class=\"nav-link\">Menu 1</summary>\r\n   <ul class=\"dropdown-menu\" role=\"menu\">\r\n       <li>Menu Item 1</li>\r\n       <li>Menu Item 2</li>\r\n   </ul>\r\n</details>\r\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\r\n   <summary class=\"nav-link\">Menu 2</summary>\r\n   <ul class=\"dropdown-menu\" role=\"menu\">\r\n       <li>Menu Item 1</li>\r\n       <li>Menu Item 2</li>\r\n   </ul>\r\n</details>\r\n</nav>\r\n```\r\n<output>\r\n<nav class=\"navbar-nav\">\r\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\r\n   <summary class=\"nav-link\">Menu 1</summary>\r\n   <ul class=\"dropdown-menu\" role=\"menu\">\r\n       <li>Menu Item 1</li>\r\n       <li>Menu Item 2</li>\r\n   </ul>\r\n</details>\r\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\r\n   <summary class=\"nav-link\">Menu 2</summary>\r\n   <ul class=\"dropdown-menu\" role=\"menu\">\r\n       <li>Menu Item 1</li>\r\n       <li>Menu Item 2</li>\r\n   </ul>\r\n</details>\r\n</nav>\r\n</output>\r\n\r\nCool, huh? Now For mobile, we can just wrap the nav inside another `details` tag because HTML allows it:\r\n\r\n```css\r\n<details class=\"nav-dropdown\">\r\n<summary class=\"nav-link\">Mobile Menu</summary>\r\n<nav class=\"navbar-nav mobile-nav\">\r\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\r\n   <summary class=\"nav-link\">Menu 1</summary>\r\n   <ul class=\"dropdown-menu\" role=\"menu\">\r\n       <li>Menu Item 1</li>\r\n       <li>Menu Item 2</li>\r\n   </ul>\r\n</details>\r\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\r\n   <summary class=\"nav-link\">Menu 2</summary>\r\n   <ul class=\"dropdown-menu\" role=\"menu\">\r\n       <li>Menu Item 1</li>\r\n       <li>Menu Item 2</li>\r\n   </ul>\r\n</details>\r\n</nav>\r\n</details>\r\n```\r\n<output>\r\n<details class=\"nav-dropdown\">\r\n<summary class=\"nav-link\">Mobile Menu</summary>\r\n<nav class=\"navbar-nav mobile-nav\">\r\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\r\n   <summary class=\"nav-link\">Menu 1</summary>\r\n   <ul class=\"dropdown-menu\" role=\"menu\">\r\n       <li>Menu Item 1</li>\r\n       <li>Menu Item 2</li>\r\n   </ul>\r\n</details>\r\n<details class=\"nav-dropdown\" name=\"interactive-menu\">\r\n   <summary class=\"nav-link\">Menu 2</summary>\r\n   <ul class=\"dropdown-menu\" role=\"menu\">\r\n       <li>Menu Item 1</li>\r\n       <li>Menu Item 2</li>\r\n   </ul>\r\n</details>\r\n</nav>\r\n</details>\r\n</output>\r\n\r\nIf you are not on a Mobile device, Resize this browser window or open this page in a mobile to see what the mobile menu will look like.\r\n\r\n### Details tag menu accessibility issues\r\n\r\n#### It still needs JavaScript\r\n\r\nYou will notice that the menu stays open even if you click outside, that's because the details tag doesn't handle clicks outside it.\r\n\r\nThe Navigation will be confusing for most users who are used to click outside or press the <kbd>Esc</kbd> key to close the menu.\r\n\r\nOne way to not have this issue would be to not use details tag, and instead use `popover` or the `dialog` element I mentioned before. But they do not work in most browsers presently used (at the time of writing) because people don't always update their browsers. So I am fine with that inconvenience. Just click the menu again to dismiss.\r\n\r\nWe can handle the clicks with JavaScript. \r\n\r\n```js\r\nwindow.addEventListener(\"click\", function (event) {\r\n  // if clicks are not on the menu button itself\r\n  if (!event.target.closest(\".nav-dropdown\")) {\r\n    // Hide all the menu if open.\r\n    Array.from(document.querySelectorAll('.nav-dropdown[open]')).forEach(\r\nmenu=>menu.removeAttribute(\"open\"))\r\n  }\r\n});\r\n```\r\n\r\nThat will work, but JavaScript is disabled on this site, so you can't test it here.\r\n\r\n\r\n#### Needs duplication of navbar\r\n\r\nTo switch between desktop and mobile navigation, if you have many items in the navbar, it is common practice to make the navbar itself a menu. Unfortunately, there is no way to show the contents of a detail tag when it is closed using any means. So the best possible way would be to create two navbars and show/hide them using media queries.\r\n\r\nYou may ask why the duplication of navbar is an accessibility issue when we can hide it. Well, the eye candy CSS can be disabled for people who don't need it. There can be network issues, or some plugin might have blocked it. Or your CSS file might be corrupted, or visitors might be using a text-only browser, the list goes on.\r\n\r\n##### Neat hidden Attribute\r\n\r\n**This section was added on 2026-01-20**\r\n\r\nOne way to tackle hiding an element in all browsers is by using the [hidden](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/hidden) global attribute. It can be [overwritten easily using CSS](https://html.spec.whatwg.org/multipage/interaction.html#the-hidden-attribute) by styling it with `display` CSS property with any value besides `none` (i.e `display:block` will show the element regardless of its `hidden` HTML attribute).\r\n\r\n[Chris Coyier](https://css-tricks.com/author/chriscoyier/) wrote on CSS tricks that <q>[The `hidden` Attribute is Visibly Weak ](https://css-tricks.com/the-hidden-attribute-is-visibly-weak/)</q>. But it is one of the best ways to optionally element when the browser does not load CSS or JavaScript.\r\n\r\nUnfortunately, even the feature-rich text-based browsers I tried does not treat the `hidden` attribute as a global one. If you are one of the developers who work on such browsers, please add support for it.\r\n\r\n## The Best Navbar\r\n\r\nThe best JS-free, responsive, semantic, and accessible navbar is to not have a navbar. Yes, you read that correctly.\r\n\r\nInstead of a navbar, there are two ways I can think of:\r\n\r\n### Make it a Navblock\r\nA navblock is a list of links on the top (header), or bottom (footer), of the page. I have seen many government websites doing this. It's mostly because they have too many pages and not everything can be listed without overwhelming the visitor. Instead, show all relevant links somewhere on the page.\r\n\r\n### Write a sentence\r\nFor small blogs and indie sites, it might be better to just write a small paragraph with the things you do and add links to them. For example, in the home page I wrote the following:\r\n\r\n> This is my personal website containing my [blogs](/blogs), [projects](/projects), [stories](/stories), other things I do, and [ways to contact me](/contact-me). It [partially supports](/am-i-indieweb-yet) the [IndieWeb](https://indieweb.org/) while keeping accessibility, minimalism, and your privacy in mind.\r\n\r\nAdding this in every page will accomplish two things:\r\n1. Some description about the author will be present in every page.\r\n2. Avoid navbar and free up some screen space.\r\n\r\nI am sure you have better ideas, let me know it in using webmention below, or just send an email :)","summary":"Once you get into web development, you will eventually start to attempt accessible development. And once you see the state of web, you will also want to create a website without JS and with less bloat.Then you hit a brick wall, it's called a navbar.","date_published":"2025-10-27T13:41:53Z","date_modified":"2026-01-20T20:16:57Z","authors":[{"name":"Coding Otaku","url":"https://codingotaku.com/users/otaku"}]},{"id":"https://codingotaku.com/blogs/adding-indieauth","url":"https://codingotaku.com/blogs/adding-indieauth","title":"Adding IndieAuth to My Website","content_html":"<p><strong>Note:</strong> Most code examples in this page are in <a href=\"https://www.rust-lang.org/\">Rust</a> and in the <a href=\"https://handlebarsjs.com/\">Handlebars template</a>. But that shouldn’t be a problem to follow this, the base logic remains the same, regardless of what language you use, the Rust just helps me avoid a few unnecessary checks.</p>\n<p>In my <a href=\"/blogs/adding-webmentions\">last post</a>, I wrote about adding <a href=\"https://www.w3.org/TR/2017/REC-Webmention-20170112/\">Webmentions</a> to this website. It ended with me saying that I will add <a href=\"https://www.w3.org/TR/indieauth\">IndieAuth</a> support next. But as I was busy being laid off and searching for a job (I’m still searching for one), I couldn’t really find much time to do it. But I also wanted a new blog post to come out at every month, so here I am trying to keep my promise.</p>\n<h2 id=\"the-basics\">The basics</h2>\n<p>The first step is to read the <a href=\"https://www.w3.org/TR/indieauth/\">IndieAuth specification</a>. I will ignore the client-side parts in this blog post, as they are irrelevant to me.</p>\n<p>What I am going to do is a <strong>single-user authentication</strong>. Since I am not allowing others to login through my website, the process is even simpler. It should be possible to change this implementation to handle multi-user authentication without much effort.</p>\n<p>For us to login to other IndieWeb webistes/apps (clients), we need an <a href=\"https://www.w3.org/TR/indieauth/#authorization-endpoint\">authorization endpoint</a>, and to allow publishing through a client, we need a <a href=\"https://www.w3.org/TR/indieauth/#token-endpoint\">token endpoint</a>.</p>\n<p>Currently, I publish all posts on my website as <a href=\"https://codeberg.org/codingotaku/website/src/branch/main/templates\">HTML files</a> in handlebars template format. Due to this, <strong>token endpoint</strong> does not make sense for me to implement right now, but I am updating this website more regularly now, so it might be a thing I do in the future!</p>\n<h2 id=\"authentication\">Authentication</h2>\n<p>Authentication is the act of validating that users are whom they claim to be. The IndieAuth specification <a href=\"https://www.w3.org/TR/indieauth/#x5-authentication\">tells us how this works</a>, let us note it down first.</p>\n<p>The endpoint that handles the request is an <strong>authorization endpoint</strong> instead of an <strong>authentication endpoint</strong>. This is because both authentication and authorization go through the same route at the beginning.</p>\n<p>In the following list, the client would be a website or app that I am logging on to, like <a href=\"https://indieweb.org\">indieweb.org</a>.</p>\n<ol>\n<li>I enter my <a href=\"https://www.w3.org/TR/indieauth/#user-profile-url\">profile URL</a> in the login form of the client and click “Sign in”</li>\n<li>The client discovers my <strong>authorization endpoint</strong> by fetching my <strong>profile URL</strong> and looking for the <code>rel=authorization_endpoint</code> value.</li>\n<li>The client builds the <a href=\"https://www.w3.org/TR/indieauth/#authentication-request\">authentication request</a> including its <a href=\"https://www.w3.org/TR/indieauth/#client-identifier\">client identifier</a>, <strong>local state</strong> (a random string), and a <a href=\"https://www.w3.org/TR/indieauth/#redirect-url\">redirect URI</a>, and redirects the browser to the <strong>authorization endpoint</strong> I am creating now.</li>\n<li>The <strong>authorization endpoint</strong> fetches the client information from the client identifier URL to have an application name and icon to display to me. (I might will this and just show the URLs to avoid complexity)</li>\n<li>The <strong>authorization endpoint</strong> prompts me to log in and asks whether to grant or deny the client’s authentication request.</li>\n<li>The <strong>authorization endpoint</strong> generates an <strong>authorization code</strong> and redirects the browser back to the client, including the <strong>local state</strong> in the URL. This is called an <a href=\"https://www.w3.org/TR/indieauth/#authentication-response\">authentication response</a>.</li>\n<li>The client <a href=\"https://www.w3.org/TR/indieauth/#authorization-code-verification\">verifies the authorization code</a> by making a POST request to the authorization endpoint. The authorization endpoint validates the authorization code, and <a href=\"https://www.w3.org/TR/indieauth/#response\">responds</a> with the End-User’s canonical profile URL.</li>\n</ol>\n<p>You can see that half of the steps are done from the client side, so it is not as complex as it looks.</p>\n<p>The <code>authorization code</code> we need to generate could be anything, the specification has not mandated a format or length, so I am thinking of just using a <a href=\"https://en.wikipedia.org/wiki/Universally_unique_identifier\">UUID</a> for this.</p>\n<h3 id=\"implementing-authentication\">Implementing Authentication</h3>\n<p>Now I know what needs to be done, and where to look for the specification, so it’s time to do some coding.</p>\n<h4 id=\"updating-the-existing-login\">Updating the existing login</h4>\n<p>I have an <a href=\"https://codeberg.org/codingotaku/website/src/commit/cd9029301d0bd7d317cecb948efbeaf5cfe48340/src/routes/admin.rs#L196\">existing login implementation</a>, this is a username and password-based authentication and I use it for moderating webmentions (previously comments). The password is hashed and salted. Once logged in, <a href=\"https://codeberg.org/codingotaku/website/src/commit/5d8e3f81540c839c11117d25778be7752de10e10/src/routes/admin.rs#L217\">I store the login status</a> in a private cookie.</p>\n<p>To support the authorization confirmation screen that I will develop later, I need to modify the login page to redirect to a <code>URL path</code> when present, so I will start with that.</p>\n<p>First, I will accept a new optional string parameter named <code>redirect_path</code> in the login request parameter, I will use this to redirect to the <code>authorization endpoint</code>.</p>\n<pre><code>#[get(\"/login-to-site?&lt;redirect_path&gt;\")]\nasync fn login_page(redirect_path: Option&lt;&amp;str&gt;, ... ) -&gt; Template {\n  ...\n    Template::render(\"admin/login-to-site\",\n      ...\n      page {\n        ...\n        redirect_path\n        ...\n      }\n      ...\n    )\n ...\n}\n</code></pre>\n<p>I used the <code>redirect_path</code> in my template as a hidden input element. Maybe I should inform myself about this redirection, but I don’t think that will be a problem as there are more manual steps to be done afterwords and I also do security checks later.</p>\n<pre><code>{{#if page.redirect_path}}\n    &lt;input type=\"hidden\" name=\"redirect_path\" value={{page.redirect_path}}&gt;\n{{/if}}\n</code></pre>\n<p>If you are interested, you can see these changes in my <a href=\"https://codeberg.org/codingotaku/website/compare/cd9029301d0bd7d317cecb948efbeaf5cfe48340..bc0b142b68792fcaa163c5ea82cf70fd86134c5f\">commit history</a>.</p>\n<p>I now need to redirect to this URL when the login POST request is successful, but I do not want to redirect to any page that comes up in the URL. I need all redirections to be within my website domain. This is as simple as making sure that the path starts with a forward slash (<code>/</code>). So I wrote a helper method to handle all in-page redirections.</p>\n<pre><code>fn redirect_to_path(mut optional_path: Option&lt;String&gt;, default: String) -&gt; Redirect {\n  let path = optional_path.take_if(|path| path.starts_with(\"/\"));\n  Redirect::to(path.unwrap_or(default))\n}\n</code></pre>\n<p>The idea is to make the <strong>login page</strong> redirect back to the <strong>authorization page</strong>, and do a final confirmation within the authorization page that will do the <strong>authentication response</strong>.</p>\n<h4 id=\"creating-the-authentication-page\">Creating the Authentication page</h4>\n<p>This is the page we would land on after trying to sign in through a client using my domain URL. I want this to be as simple as possible, and I am also thinking of adding the <strong>token endpoint</strong> support to it later.</p>\n<p>Anyway, I went back and read the specification again to get an example of what we will get from the client, and it is a <code>GET</code> request like this:</p>\n<pre><code>https://example.org/auth?me=https://user.example.net/&amp;\n                          redirect_uri=https://app.example.com/redirect&amp;\n                          client_id=https://app.example.com/&amp;\n                          state=1234567890&amp;\n                          response_type=id\n</code></pre>\n<p>Looking at the specification, I know the following:</p>\n<ul>\n<li>The parameter <code>me</code> should be my domain URL in its canonical form (e.g. <code>https://codingotaku.com/</code>).</li>\n<li>The <code>redirect_uri</code> is the URI I need to redirect to after authorization.</li>\n<li>The <code>client_id</code> is the URI of the app.</li>\n<li>The <code>state</code> is a value that I need to send to the client as it is to avoid cross-site request forgery.</li>\n<li>And finally, the optional <code>response_type</code> that defaults to <code>id</code> means that this is an authentication request.</li>\n</ul>\n<p>If the <code>response_type</code> is <code>code</code>, that means that it is an <strong>authorization</strong> request instead of an <strong>authentication</strong> request, we will handle this later, for now, I will treat all requests as an <strong>authentication</strong> request.</p>\n<p>Ideally, the <code>redirect_uri</code> would be using the same host and port as the <code>client_id</code>. But this might not be the case always. So we need to ensure that the client supports the <code>redirect_uri</code> provided by crawling the client. But I decided to just accept those requests in the initial version, and add that in after finding a good HTML parsing library for rust.</p>\n<p>As the <a href=\"https://www.w3.org/TR/indieauth/#x4-2-1-application-information\">specification tells</a>, it is important to show as much detail as possible in authorization page. My idea is to display the <code>response_type</code>, <code>client_id</code>, and the <code>redirect_uri</code> on the page. So that I can do a manual verification before logging in. This way, I can catch the abnormalities, like <code>client_id</code> and <code>redirect_uri</code> not matching.</p>\n<p>The page code will look something like this (written in handlebars template):</p>\n<pre><code>&lt;p&gt;You are receiving a request to authenticate to &lt;a href=\"{{page.client_id}}\"&gt;{{page.client_id}}&lt;/a&gt;&lt;/p&gt;\n&lt;p&gt;After authentication, this page will be redirected to &lt;code&gt;{{page.redirect_uri}}&lt;/code&gt;&lt;/p&gt;\n&lt;p&gt;If this request looks suspicious, please manually verify them, or avoid authenticating the request.&lt;/p&gt;\n\n&lt;h2&gt;Request details&lt;/h2&gt;\n&lt;dl&gt;\n  &lt;dt&gt;&lt;strong&gt;Client ID&lt;/strong&gt;&lt;/dt&gt;\n  &lt;dd&gt;{{page.client_id}}&lt;/dd&gt;\n  &lt;dt&gt;&lt;strong&gt;Redirect URI&lt;/strong&gt;&lt;/dt&gt;\n  &lt;dd&gt;{{page.redirect_uri}}&lt;/dd&gt;\n  &lt;dt&gt;&lt;strong&gt;Response type&lt;/strong&gt;&lt;/dt&gt;\n  &lt;dd&gt;{{page.response_type}}&lt;/dd&gt;\n&lt;/dl&gt;\n</code></pre>\n<p>The authentication page needs to check whether I am logged in or not, and If I am logged in, a button should be shown to do the authentication; otherwise, I should be asked to log in.</p>\n<pre><code>{{#if settings.is_logged_in}}\n&lt;form class=\"form\" action=\"/authentication\" method=\"post\" accept-charset=\"utf-8\" aria-label=\"Authentication\"&gt;\n  &lt;input type=\"hidden\" name=\"redirect_uri\" value={{page.redirect_uri}}&gt;\n  &lt;input type=\"hidden\" name=\"response_type\" value={{page.response_type}}&gt;\n  &lt;input type=\"hidden\" name=\"state\" value={{page.state}}&gt;\n  &lt;input type=\"hidden\" name=\"client_id\" value={{page.client_id}}&gt;\n  &lt;input type=\"hidden\" name=\"me\" value={{page.me}}&gt;\n  &lt;button type=\"submit\" class=\"submit-button\"&gt;Authenticate&lt;/button&gt;\n&lt;/form&gt;\n{{else}}\n  &lt;p class=\"space-out\"&gt;&lt;a class=\"link-button\" href=\"/login-to-site?redirect_path={{page.escaped_uri}}\"&gt;Login to Authenticate this request&lt;/a&gt;&lt;/p&gt;\n{{/if}}\n</code></pre>\n<p>Here is a screenshot of the authentication page before logging in:</p>\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/5de790e7-56e8-4116-9bda-0ea66bdccb30.png\" alt=\"Screenshot before logging in\" width=\"790\" height=\"588\"><figcaption>The authentication request page has the client ID, redirect URI, and the response type with a link to login. I placed a warning to not proceed if the request looks suspicious.</figcaption></figure>\n<p>After logging in, the only change is that I replaced the login link with a button to authenticate the request, here is the screenshot:</p>\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/53477476-6bd6-40be-bc74-c5f239a3b17e.png\" alt=\"Screenshot after logging in\" width=\"785\" height=\"579\"><figcaption>Same content as the previous screenshot, with the link replaced with anAuthenticatebutton placed in the centre.</figcaption></figure>\n<p>The complete page template can be found <a href=\"https://codeberg.org/codingotaku/website/src/commit/5d8e3f81540c839c11117d25778be7752de10e10/templates/authentication-page.html.hbs\">in my repository</a>.</p>\n<h4 id=\"creating-a-table-to-store-access-codes\">Creating a table to store access codes</h4>\n<p>To keep track of the authentication requests, I will create a new table named <code>indie_auth_code</code>. Within this, I will add all <strong>six</strong> columns that I will be needing for authentication.</p>\n<ol>\n<li><code>id</code>: for me to query them later.</li>\n<li><code>client_id</code>: The client requesting for authentication.</li>\n<li><code>redirect_uri</code>: The redirect URI send by the client.</li>\n<li><code>code</code>: a unique authentication/authorization code.</li>\n<li><code>created_on</code>: The date and time of creating this row.</li>\n<li><code>expire_on</code>: the date and time when the code expires.</li>\n</ol>\n<p>I use <a href=\"https://www.sqlite.org/\">SQLite</a> for storing things on my website, and the table schema looks like this:</p>\n<pre><code>CREATE TABLE indie_auth_code (\n  id TEXT NOT NULL,\n  client_id TEXT NOT NULL,\n  redirect_uri TEXT NOT NULL,\n  code TEXT NOT NULL,\n  created_on DATETIME NOT NULL,\n  expires_on DATETIME NOT NULL,\n  UNIQUE(code)\n);\n</code></pre>\n<h4 id=\"authentication-endpoint\">Authentication endpoint</h4>\n<p>Now that we have a table, we reached the main part of the whole process. It is time to do the actual authentication.</p>\n<p>I created a new endpoint named <code>authentication</code>. This receives a <code>POST</code> request that contains all values that will be sent by the client. The response will always be a redirection.</p>\n<pre><code>#[derive(FromForm)]\npub(crate) struct IndieAuthForm {\n    pub me: String,\n    pub client_id: String,\n    pub redirect_uri: String,\n    pub state: String,\n    pub response_type: Option&lt;String&gt;,\n}\n\n#[post(\"/authentication\", data = \"&lt;indie_auth&gt;\")]\nasync fn authentication(indie_auth: Form&lt;IndieAuthForm&gt;) -&gt; Redirect {\n  ...\n}\n</code></pre>\n<p>You will notice that <code>reponse_type</code> is an <code>Option</code>, this is because it is an optional parameter that defaults to the string <code>\"id\"</code>.</p>\n<p>The <code>authentication</code> endpoint will first ensure that both the <code>client_id</code> and <code>rediect_uri</code> are of the same host and port. I wrote a few helper methods for this:</p>\n<pre><code>fn is_port_matching(source: Option&lt;u16&gt;, target: Option&lt;u16&gt;) -&gt; bool {\n    (source.is_none() &amp;&amp; target.is_none())\n        || (source.is_some_and(|_| target.is_some()) &amp;&amp; source.unwrap().eq(&amp;target.unwrap()))\n}\n\nfn is_host_matching(source: &amp;str, target: &amp;str) -&gt; bool {\n    !(source.is_empty() || target.is_empty() || source.ne(target))\n}\n\nfn get_url_authority(uri_value: &amp;str) -&gt; Option&lt;Authority&gt; {\n    let url = uri::Absolute::parse(uri_value);\n\n    url.map_or(None, |url| url.authority().cloned())\n}\n\nfn is_authority_matching(source_uri: &amp;str, target_uri: &amp;str) -&gt; bool {\n    let source = get_url_authority(source_uri);\n    let target = get_url_authority(target_uri);\n\n    source.is_some_and(|source_val| {\n        target.is_some_and(|target_val| {\n            is_host_matching(source_val.host(), target_val.host())\n                &amp;&amp; is_port_matching(source_val.port(), target_val.port())\n        })\n    })\n}\n</code></pre>\n<p>The helper methods will handle all edge cases of varying ports and host names, but if they differ, I will need to <a href=\"https://www.w3.org/TR/indieauth/#redirect-url\">crawl the client to find the redirect_uri</a>. As I <a href=\"#back-ref-1\">mentioned before</a>, I do this manually now. I’ll update this blog once I find time to do a proper check.</p>\n<p>Once the first check was done, I generated a new UUID, and stored it in the <code>indie_auth_code</code> table we created before. We already have all the details we need, the <code>created_on</code> is the current time and <code>expires_on</code> will be around 5 minutes past the current time.</p>\n<pre><code>let code = Uuid::new_v4().to_string();\nlet now = OffsetDateTime::now_utc();\n\nlet auth_code = IndieAuthCode {\n    id: Uuid::new_v4().to_string(),\n    client_id: indie_auth.client_id.clone(),\n    redirect_uri: indie_auth.redirect_uri.clone(),\n    code: code.clone(),\n    created_on: now.format(&amp;well_known::Iso8601::DEFAULT).unwrap(),\n    expires_on: now\n        .checked_add(Duration::minutes(5))\n        .unwrap()\n        .format(&amp;well_known::Iso8601::DEFAULT)\n        .unwrap(),\n};\n\nadd_auth_code(&amp;mut db, &amp;auth_code).await;\n</code></pre>\n<p>The <a href=\"https://www.w3.org/TR/indieauth/#x5-3-authentication-response\">specification suggests</a> a maximum of 10 minutes for the expiration time, but let us play a little safe and keep it at 5. I want it to change the time limit via a config or something in the future.</p>\n<p>On hindsight, setting time is something <a href=\"https://www.sqlite.org/lang_datefunc.html\">I could do from the database</a> itself, but this is what we are going with right now.</p>\n<p>I sent it to <code>redirect_uri</code> along with the given <code>state</code> as an HTTP redirect with <code>302 Found</code> status.</p>\n<p>You can find the full implementation with more error handling <a href=\"https://codeberg.org/codingotaku/website/src/commit/2ca1531c21263a436a1bb4feba7407dd928ddd5e/src/routes/indie_auth.rs#L142\">in my repository</a>.</p>\n<p>Everything is coming together, now as per the specification, I need to accept another <a href=\"https://www.w3.org/TR/indieauth/#x5-4-authorization-code-verification\">Authorization Code Verification</a> request being sent by the client to the <strong>authorization endpoint</strong>.</p>\n<h4 id=\"authorization-code-verification\">Authorization Code Verification</h4>\n<p>The validation part is simple, the client will give us the <code>code</code>, <code>client_id</code>, and the <code>redirect_uri</code> as the <code>POST</code> parameter. We just need to return the profile URL as a <code>JSON</code> to the client after the validating it against the details we saved in the last step within the <code>indie_auth_code</code> table. My response will need to look like this:</p>\n<pre><code>{\n  \"me\": \"https://codingotaku.com/\"\n}\n</code></pre>\n<p>In case of errors, I am just sending the error code instead, something like this:</p>\n<pre><code>{\n  \"error\": \"invald_request\"\n}\n</code></pre>\n<p>All validations can be done from the database itself, so I wrote this query:</p>\n<pre><code>SELECT id, client_id, redirect_uri, code, created_on, expires_on, is_accessed\n       FROM indie_auth_code\n       WHERE code=[the-received-cde]\n       AND client_id=[received-client-id]\n       AND redirect_uri=[received-redirect-uri]\n       AND datetime(expires_on) &gt; datetime('now');\n</code></pre>\n<p>The query is being used by my <code>authorization</code> endpoint, I also do the redundant checks to ensure that the request is valid before doing the query, this is not really needed, but it can prevent an unnecessary query.</p>\n<pre><code>#[post(\"/indie-auth\", data = \"&lt;code_verification&gt;\")]\nasync fn auth_verification(\n    mut db: Connection&lt;Db&gt;,\n    code_verification: Form&lt;IndieAuthCodeVerification&gt;,\n    config: &amp;State&lt;AppConfig&gt;,\n) -&gt; (Status, Json&lt;IndieAuthResponse&gt;) {\n    if !is_authority_matching(\n        &amp;code_verification.client_id,\n        &amp;code_verification.redirect_uri,\n    ) {\n        return (\n            Status::BadRequest,\n            Json(IndieAuthResponse {\n                me: None,\n                error: Some(String::from(\"invalid_request\")),\n            }),\n        );\n    }\n\n    if let Ok(auth_code) = get_auth_code(&amp;mut db, &amp;code_verification).await {\n        delete_auth_code(&amp;mut db, &amp;auth_code.id).await;\n        (\n            Status::Found,\n            Json(IndieAuthResponse {\n                me: format!(\"{}/\", config.card.homepage).into(),\n                error: None,\n            }),\n        )\n    } else {\n        (\n            Status::NotFound,\n            Json(IndieAuthResponse {\n                me: None,\n                error: Some(String::from(\"invalid_grant\")),\n            }),\n        )\n    }\n}\n</code></pre>\n<h4 id=\"using-the-authorization-endpoint\">Using the authorization endpoint</h4>\n<p>The final step is to actually use this endpoint. Until now, I was using <code>indieauth.com/auth</code> as the endpoint, I updated it to the new <code>codingotaku.com/indie-auth</code>.</p>\n<p>Note: If you go to that page without proper request parameters, I will just show you a <strong>404 not found</strong> page, this is to trick some of the bad bots that I’ve been getting.</p>\n<p>If you would like to see the complete set of changes, <a href=\"https://codeberg.org/codingotaku/website/compare/cd9029301d0bd7d317cecb948efbeaf5cfe48340..2ca1531c21263a436a1bb4feba7407dd928ddd5e\">here is the commit difference</a> between the implementations (you’ll need to scroll down a bit to see the files).</p>\n<h3 id=\"the-issues-i-faced\">The issues I faced</h3>\n<p>My initial approach was to reject the <strong>authentication request</strong> if the <code>response_type</code> is not “id” or empty. But all the clients I tried to log in sent me the <code>response_type</code> as “code” instead. Annoyingly, it is also true for the <a href=\"https://indieweb.org\">indieweb.org</a>. It could either be a bug, or they have more features that I can unlock once I add a <strong>token endpoint</strong>.</p>\n<p>In my <a href=\"https://codeberg.org/codingotaku/website/src/commit/2ca1531c21263a436a1bb4feba7407dd928ddd5e/src/routes/indie_auth.rs#L163\">current implementation</a>, I am just printing this on to the console and ignoring it (I know, shut up). But I will need to handle this by creating a <strong>token endpoint</strong> later.</p>\n<p>Other than this one thing, I haven’t really faced any other issues, hope this helps, now I am having 100% IndieWeb implementation done by myself without depending on a service!</p>\n<h2 id=\"next-steps\">Next steps</h2>\n<p>As I have mentioned a few times in the article, I am still missing a <strong>token endpoint</strong>. But I have some work already being done on a separate branch to use a database for all the posts. It is a big task as I need to think of better backup systems in case I corrupt the DB.</p>\n<p>Once the database work is done, I will be able to add a token endpoint and post without updating the repository all the time 😄.</p>\n","content_text":"**Note:** Most code examples in this page are in [Rust](https://www.rust-lang.org/) and in the [Handlebars template](https://handlebarsjs.com/). But that shouldn't be a problem to follow this, the base logic remains the same, regardless of what language you use, the Rust just helps me avoid a few unnecessary checks.\n\nIn my [last post](/blogs/adding-webmentions), I wrote about adding [Webmentions](https://www.w3.org/TR/2017/REC-Webmention-20170112/) to this website. It ended with me saying that I will add [IndieAuth](https://www.w3.org/TR/indieauth) support next. But as I was busy being laid off and searching for a job (I'm still searching for one), I couldn't really find much time to do it. But I also wanted a new blog post to come out at every month, so here I am trying to keep my promise.\n\n## The basics\n\nThe first step is to read the [IndieAuth specification](https://www.w3.org/TR/indieauth/). I will ignore the client-side parts in this blog post, as they are irrelevant to me.\n\nWhat I am going to do is a **single-user authentication**. Since I am not allowing others to login through my website, the process is even simpler. It should be possible to change this implementation to handle multi-user authentication without much effort.\n\nFor us to login to other IndieWeb webistes/apps (clients), we need an [authorization endpoint](https://www.w3.org/TR/indieauth/#authorization-endpoint), and to allow publishing through a client, we need a [token endpoint](https://www.w3.org/TR/indieauth/#token-endpoint).\n\nCurrently, I publish all posts on my website as [HTML files](https://codeberg.org/codingotaku/website/src/branch/main/templates) in handlebars template format. Due to this, **token endpoint** does not make sense for me to implement right now, but I am updating this website more regularly now, so it might be a thing I do in the future!\n\n## Authentication\n\nAuthentication is the act of validating that users are whom they claim to be. The IndieAuth specification [tells us how this works](https://www.w3.org/TR/indieauth/#x5-authentication), let us note it down first.\n\nThe endpoint that handles the request is an **authorization endpoint** instead of an **authentication endpoint**. This is because both authentication and authorization go through the same route at the beginning.\n\nIn the following list, the client would be a website or app that I am logging on to, like [indieweb.org](https://indieweb.org).\n\n1. I enter my [profile URL](https://www.w3.org/TR/indieauth/#user-profile-url) in the login form of the client and click \"Sign in\"\n2. The client discovers my **authorization endpoint** by fetching my **profile URL** and looking for the `rel=authorization_endpoint` value.\n3. The client builds the [authentication request](https://www.w3.org/TR/indieauth/#authentication-request) including its [client identifier](https://www.w3.org/TR/indieauth/#client-identifier), **local state** (a random string), and a [redirect URI](https://www.w3.org/TR/indieauth/#redirect-url), and redirects the browser to the **authorization endpoint** I am creating now.\n4. The **authorization endpoint** fetches the client information from the client identifier URL to have an application name and icon to display to me. (I might will this and just show the URLs to avoid complexity)\n5. The **authorization endpoint** prompts me to log in and asks whether to grant or deny the client's authentication request.\n6. The **authorization endpoint** generates an **authorization code** and redirects the browser back to the client, including the **local state** in the URL. This is called an [authentication response](https://www.w3.org/TR/indieauth/#authentication-response).\n7. The client [verifies the authorization code](https://www.w3.org/TR/indieauth/#authorization-code-verification) by making a POST request to the authorization endpoint. The authorization endpoint validates the authorization code, and [responds](https://www.w3.org/TR/indieauth/#response) with the End-User's canonical profile URL.\n\nYou can see that half of the steps are done from the client side, so it is not as complex as it looks.\n\nThe `authorization code` we need to generate could be anything, the specification has not mandated a format or length, so I am thinking of just using a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) for this.\n\n### Implementing Authentication\n\nNow I know what needs to be done, and where to look for the specification, so it's time to do some coding.\n\n#### Updating the existing login\n\nI have an [existing login implementation](https://codeberg.org/codingotaku/website/src/commit/cd9029301d0bd7d317cecb948efbeaf5cfe48340/src/routes/admin.rs#L196), this is a username and password-based authentication and I use it for moderating webmentions (previously comments). The password is hashed and salted. Once logged in, [I store the login status](https://codeberg.org/codingotaku/website/src/commit/5d8e3f81540c839c11117d25778be7752de10e10/src/routes/admin.rs#L217) in a private cookie.\n\nTo support the authorization confirmation screen that I will develop later, I need to modify the login page to redirect to a `URL path` when present, so I will start with that.\n\nFirst, I will accept a new optional string parameter named `redirect_path` in the login request parameter, I will use this to redirect to the `authorization endpoint`.\n\n```\n#[get(\"/login-to-site?<redirect_path>\")]\nasync fn login_page(redirect_path: Option<&str>, ... ) -> Template {\n  ...\n    Template::render(\"admin/login-to-site\",\n      ...\n      page {\n        ...\n        redirect_path\n        ...\n      }\n      ...\n    )\n ...\n}\n```\n\nI used the `redirect_path` in my template as a hidden input element. Maybe I should inform myself about this redirection, but I don't think that will be a problem as there are more manual steps to be done afterwords and I also do security checks later.\n\n```\n{{#if page.redirect_path}}\n    <input type=\"hidden\" name=\"redirect_path\" value={{page.redirect_path}}>\n{{/if}}\n```\n\nIf you are interested, you can see these changes in my [commit history](https://codeberg.org/codingotaku/website/compare/cd9029301d0bd7d317cecb948efbeaf5cfe48340..bc0b142b68792fcaa163c5ea82cf70fd86134c5f).\n\nI now need to redirect to this URL when the login POST request is successful, but I do not want to redirect to any page that comes up in the URL. I need all redirections to be within my website domain. This is as simple as making sure that the path starts with a forward slash (`/`). So I wrote a helper method to handle all in-page redirections.\n\n```\nfn redirect_to_path(mut optional_path: Option<String>, default: String) -> Redirect {\n  let path = optional_path.take_if(|path| path.starts_with(\"/\"));\n  Redirect::to(path.unwrap_or(default))\n}\n```\n\nThe idea is to make the **login page** redirect back to the **authorization page**, and do a final confirmation within the authorization page that will do the **authentication response**.\n\n#### Creating the Authentication page\n\nThis is the page we would land on after trying to sign in through a client using my domain URL. I want this to be as simple as possible, and I am also thinking of adding the **token endpoint** support to it later.\n\nAnyway, I went back and read the specification again to get an example of what we will get from the client, and it is a `GET` request like this:\n\n```\nhttps://example.org/auth?me=https://user.example.net/&\n                          redirect_uri=https://app.example.com/redirect&\n                          client_id=https://app.example.com/&\n                          state=1234567890&\n                          response_type=id\n```\n\nLooking at the specification, I know the following:\n\n- The parameter `me` should be my domain URL in its canonical form (e.g. `https://codingotaku.com/`).\n- The `redirect_uri` is the URI I need to redirect to after authorization.\n- The `client_id` is the URI of the app.\n- The `state` is a value that I need to send to the client as it is to avoid cross-site request forgery.\n- And finally, the optional `response_type` that defaults to `id` means that this is an authentication request.\n\nIf the `response_type` is `code`, that means that it is an **authorization** request instead of an **authentication** request, we will handle this later, for now, I will treat all requests as an **authentication** request.\n\nIdeally, the `redirect_uri` would be using the same host and port as the `client_id`. But this might not be the case always. So we need to ensure that the client supports the `redirect_uri` provided by crawling the client. But I decided to just accept those requests in the initial version, and add that in after finding a good HTML parsing library for rust.\n\nAs the [specification tells](https://www.w3.org/TR/indieauth/#x4-2-1-application-information), it is important to show as much detail as possible in authorization page. My idea is to display the `response_type`, `client_id`, and the `redirect_uri` on the page. So that I can do a manual verification before logging in. This way, I can catch the abnormalities, like `client_id` and `redirect_uri` not matching.\n\nThe page code will look something like this (written in handlebars template):\n\n```\n<p>You are receiving a request to authenticate to <a href=\"{{page.client_id}}\">{{page.client_id}}</a></p>\n<p>After authentication, this page will be redirected to <code>{{page.redirect_uri}}</code></p>\n<p>If this request looks suspicious, please manually verify them, or avoid authenticating the request.</p>\n\n<h2>Request details</h2>\n<dl>\n  <dt><strong>Client ID</strong></dt>\n  <dd>{{page.client_id}}</dd>\n  <dt><strong>Redirect URI</strong></dt>\n  <dd>{{page.redirect_uri}}</dd>\n  <dt><strong>Response type</strong></dt>\n  <dd>{{page.response_type}}</dd>\n</dl>\n```\n\nThe authentication page needs to check whether I am logged in or not, and If I am logged in, a button should be shown to do the authentication; otherwise, I should be asked to log in.\n\n```\n{{#if settings.is_logged_in}}\n<form class=\"form\" action=\"/authentication\" method=\"post\" accept-charset=\"utf-8\" aria-label=\"Authentication\">\n  <input type=\"hidden\" name=\"redirect_uri\" value={{page.redirect_uri}}>\n  <input type=\"hidden\" name=\"response_type\" value={{page.response_type}}>\n  <input type=\"hidden\" name=\"state\" value={{page.state}}>\n  <input type=\"hidden\" name=\"client_id\" value={{page.client_id}}>\n  <input type=\"hidden\" name=\"me\" value={{page.me}}>\n  <button type=\"submit\" class=\"submit-button\">Authenticate</button>\n</form>\n{{else}}\n  <p class=\"space-out\"><a class=\"link-button\" href=\"/login-to-site?redirect_path={{page.escaped_uri}}\">Login to Authenticate this request</a></p>\n{{/if}}\n```\n\nHere is a screenshot of the authentication page before logging in:\n\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/5de790e7-56e8-4116-9bda-0ea66bdccb30.png\" alt=\"Screenshot before logging in\" width=\"790\" height=\"588\"><figcaption>The authentication request page has the client ID, redirect URI, and the response type with a link to login. I placed a warning to not proceed if the request looks suspicious.</figcaption></figure>\n\nAfter logging in, the only change is that I replaced the login link with a button to authenticate the request, here is the screenshot:\n\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/53477476-6bd6-40be-bc74-c5f239a3b17e.png\" alt=\"Screenshot after logging in\" width=\"785\" height=\"579\"><figcaption>Same content as the previous screenshot, with the link replaced with anAuthenticatebutton placed in the centre.</figcaption></figure>\n\nThe complete page template can be found [in my repository](https://codeberg.org/codingotaku/website/src/commit/5d8e3f81540c839c11117d25778be7752de10e10/templates/authentication-page.html.hbs).\n\n#### Creating a table to store access codes\n\nTo keep track of the authentication requests, I will create a new table named `indie_auth_code`. Within this, I will add all **six** columns that I will be needing for authentication.\n\n1. `id`: for me to query them later.\n2. `client_id`: The client requesting for authentication.\n3. `redirect_uri`: The redirect URI send by the client.\n4. `code`: a unique authentication/authorization code.\n5. `created_on`: The date and time of creating this row.\n6. `expire_on`: the date and time when the code expires.\n\nI use [SQLite](https://www.sqlite.org/) for storing things on my website, and the table schema looks like this:\n\n```\nCREATE TABLE indie_auth_code (\n  id TEXT NOT NULL,\n  client_id TEXT NOT NULL,\n  redirect_uri TEXT NOT NULL,\n  code TEXT NOT NULL,\n  created_on DATETIME NOT NULL,\n  expires_on DATETIME NOT NULL,\n  UNIQUE(code)\n);\n```\n\n#### Authentication endpoint\n\nNow that we have a table, we reached the main part of the whole process. It is time to do the actual authentication.\n\nI created a new endpoint named `authentication`. This receives a `POST` request that contains all values that will be sent by the client. The response will always be a redirection.\n\n```\n#[derive(FromForm)]\npub(crate) struct IndieAuthForm {\n    pub me: String,\n    pub client_id: String,\n    pub redirect_uri: String,\n    pub state: String,\n    pub response_type: Option<String>,\n}\n\n#[post(\"/authentication\", data = \"<indie_auth>\")]\nasync fn authentication(indie_auth: Form<IndieAuthForm>) -> Redirect {\n  ...\n}\n```\n\nYou will notice that `reponse_type` is an `Option`, this is because it is an optional parameter that defaults to the string `\"id\"`.\n\nThe `authentication` endpoint will first ensure that both the `client_id` and `rediect_uri` are of the same host and port. I wrote a few helper methods for this:\n\n```\nfn is_port_matching(source: Option<u16>, target: Option<u16>) -> bool {\n    (source.is_none() && target.is_none())\n        || (source.is_some_and(|_| target.is_some()) && source.unwrap().eq(&target.unwrap()))\n}\n\nfn is_host_matching(source: &str, target: &str) -> bool {\n    !(source.is_empty() || target.is_empty() || source.ne(target))\n}\n\nfn get_url_authority(uri_value: &str) -> Option<Authority> {\n    let url = uri::Absolute::parse(uri_value);\n\n    url.map_or(None, |url| url.authority().cloned())\n}\n\nfn is_authority_matching(source_uri: &str, target_uri: &str) -> bool {\n    let source = get_url_authority(source_uri);\n    let target = get_url_authority(target_uri);\n\n    source.is_some_and(|source_val| {\n        target.is_some_and(|target_val| {\n            is_host_matching(source_val.host(), target_val.host())\n                && is_port_matching(source_val.port(), target_val.port())\n        })\n    })\n}\n```\n\nThe helper methods will handle all edge cases of varying ports and host names, but if they differ, I will need to [crawl the client to find the redirect\\_uri](https://www.w3.org/TR/indieauth/#redirect-url). As I [mentioned before](#back-ref-1), I do this manually now. I'll update this blog once I find time to do a proper check.\n\nOnce the first check was done, I generated a new UUID, and stored it in the `indie_auth_code` table we created before. We already have all the details we need, the `created_on` is the current time and `expires_on` will be around 5 minutes past the current time.\n\n```\nlet code = Uuid::new_v4().to_string();\nlet now = OffsetDateTime::now_utc();\n\nlet auth_code = IndieAuthCode {\n    id: Uuid::new_v4().to_string(),\n    client_id: indie_auth.client_id.clone(),\n    redirect_uri: indie_auth.redirect_uri.clone(),\n    code: code.clone(),\n    created_on: now.format(&well_known::Iso8601::DEFAULT).unwrap(),\n    expires_on: now\n        .checked_add(Duration::minutes(5))\n        .unwrap()\n        .format(&well_known::Iso8601::DEFAULT)\n        .unwrap(),\n};\n\nadd_auth_code(&mut db, &auth_code).await;\n```\n\nThe [specification suggests](https://www.w3.org/TR/indieauth/#x5-3-authentication-response) a maximum of 10 minutes for the expiration time, but let us play a little safe and keep it at 5. I want it to change the time limit via a config or something in the future.\n\nOn hindsight, setting time is something [I could do from the database](https://www.sqlite.org/lang_datefunc.html) itself, but this is what we are going with right now.\n\nI sent it to `redirect_uri` along with the given `state` as an HTTP redirect with `302 Found` status.\n\nYou can find the full implementation with more error handling [in my repository](https://codeberg.org/codingotaku/website/src/commit/2ca1531c21263a436a1bb4feba7407dd928ddd5e/src/routes/indie_auth.rs#L142).\n\nEverything is coming together, now as per the specification, I need to accept another [Authorization Code Verification](https://www.w3.org/TR/indieauth/#x5-4-authorization-code-verification) request being sent by the client to the **authorization endpoint**.\n\n#### Authorization Code Verification\n\nThe validation part is simple, the client will give us the `code`, `client_id`, and the `redirect_uri` as the `POST` parameter. We just need to return the profile URL as a `JSON` to the client after the validating it against the details we saved in the last step within the `indie_auth_code` table. My response will need to look like this:\n\n```\n{\n  \"me\": \"https://codingotaku.com/\"\n}\n```\n\nIn case of errors, I am just sending the error code instead, something like this:\n\n```\n{\n  \"error\": \"invald_request\"\n}\n```\n\nAll validations can be done from the database itself, so I wrote this query:\n\n```\nSELECT id, client_id, redirect_uri, code, created_on, expires_on, is_accessed\n       FROM indie_auth_code\n       WHERE code=[the-received-cde]\n       AND client_id=[received-client-id]\n       AND redirect_uri=[received-redirect-uri]\n       AND datetime(expires_on) > datetime('now');\n```\n\nThe query is being used by my `authorization` endpoint, I also do the redundant checks to ensure that the request is valid before doing the query, this is not really needed, but it can prevent an unnecessary query.\n\n```\n#[post(\"/indie-auth\", data = \"<code_verification>\")]\nasync fn auth_verification(\n    mut db: Connection<Db>,\n    code_verification: Form<IndieAuthCodeVerification>,\n    config: &State<AppConfig>,\n) -> (Status, Json<IndieAuthResponse>) {\n    if !is_authority_matching(\n        &code_verification.client_id,\n        &code_verification.redirect_uri,\n    ) {\n        return (\n            Status::BadRequest,\n            Json(IndieAuthResponse {\n                me: None,\n                error: Some(String::from(\"invalid_request\")),\n            }),\n        );\n    }\n\n    if let Ok(auth_code) = get_auth_code(&mut db, &code_verification).await {\n        delete_auth_code(&mut db, &auth_code.id).await;\n        (\n            Status::Found,\n            Json(IndieAuthResponse {\n                me: format!(\"{}/\", config.card.homepage).into(),\n                error: None,\n            }),\n        )\n    } else {\n        (\n            Status::NotFound,\n            Json(IndieAuthResponse {\n                me: None,\n                error: Some(String::from(\"invalid_grant\")),\n            }),\n        )\n    }\n}\n```\n\n#### Using the authorization endpoint\n\nThe final step is to actually use this endpoint. Until now, I was using `indieauth.com/auth` as the endpoint, I updated it to the new `codingotaku.com/indie-auth`.\n\nNote: If you go to that page without proper request parameters, I will just show you a **404 not found** page, this is to trick some of the bad bots that I've been getting.\n\nIf you would like to see the complete set of changes, [here is the commit difference](https://codeberg.org/codingotaku/website/compare/cd9029301d0bd7d317cecb948efbeaf5cfe48340..2ca1531c21263a436a1bb4feba7407dd928ddd5e) between the implementations (you'll need to scroll down a bit to see the files).\n\n### The issues I faced\n\nMy initial approach was to reject the **authentication request** if the `response_type` is not \"id\" or empty. But all the clients I tried to log in sent me the `response_type` as \"code\" instead. Annoyingly, it is also true for the [indieweb.org](https://indieweb.org). It could either be a bug, or they have more features that I can unlock once I add a **token endpoint**.\n\nIn my [current implementation](https://codeberg.org/codingotaku/website/src/commit/2ca1531c21263a436a1bb4feba7407dd928ddd5e/src/routes/indie_auth.rs#L163), I am just printing this on to the console and ignoring it (I know, shut up). But I will need to handle this by creating a **token endpoint** later.\n\nOther than this one thing, I haven't really faced any other issues, hope this helps, now I am having 100% IndieWeb implementation done by myself without depending on a service!\n\n## Next steps\n\nAs I have mentioned a few times in the article, I am still missing a **token endpoint**. But I have some work already being done on a separate branch to use a database for all the posts. It is a big task as I need to think of better backup systems in case I corrupt the DB.\n\nOnce the database work is done, I will be able to add a token endpoint and post without updating the repository all the time 😄.","summary":"A single user IndieAuth is surprisingly easy to implement! This post is about how I did it.","date_published":"2024-09-14T12:32:33Z","date_modified":"2024-09-14T17:53:49Z","authors":[{"name":"Coding Otaku","url":"https://codingotaku.com/users/otaku"}]},{"id":"https://codingotaku.com/blogs/adding-webmentions","url":"https://codingotaku.com/blogs/adding-webmentions","title":"Adding Webmentions to My Website","content_html":"<p>I stopped accepting comments in favour of <a href=\"https://www.w3.org/TR/2017/REC-Webmention-20170112/\">Webmentions</a> a while back. This was not a decision we made lightly, especially because I put a lot of time and energy in adding <a href=\"/blogs/adding-comments\">comment support recently</a>.</p>\n<h2 id=\"why-remove-comments\">Why remove comments?</h2>\n<p>While I was able to fully moderate the comments and reduced the spams to near zero, I also need to be aware of the privacy concerns of the people commenting on my website.</p>\n<p>I tackled this by letting people comment anonymously, but I still allow them to share their name, email, etc., if they like.</p>\n<p>There are some problems with this approach:</p>\n<p><strong>Validity:</strong>\n:   I have no way to ensure that the person commenting on my website is really the person whom they are claiming to be.</p>\n<p><strong>Spams and trolls:</strong>\n:   While I have managed to prevent all automated spams, people can manually troll my website, adding a gate like moderation to block this behaviour helps, but is still not good for my mental health.</p>\n<p><strong>Interactivity:</strong>\n:   People usually comment and expect a response, but my previous system was treating it like shouting at the void, so it’s not a good user experience.</p>\n<p><strong>Visibility:</strong>\n:   If someone comments or mentions my website on <em>their</em> website, that is also a comment, there is no way for me to track it unless they enter their whole comment again on my website.</p>\n<h2 id=\"why-webmention\">Why Webmention?</h2>\n<p>Some of you already know this, <a href=\"/am-i-indieweb-yet\">I partially support IndieWeb</a> on this website. One of the major things that I was missing for in it was Webmentions. Unlike supporting comments, Webmentions makes it easy to filter out things that I don’t want and works as a better gatekeeper than manual moderation (though I still moderate the Webmentions I receive).</p>\n<p>Webmention is a W3 standard to let websites talk to each other, it could be a comment, like, a simple reference, or whatever. The idea is that, if “Website A” has a link that mentions “Website B”, the “Website A” or someone else can tell “Website B” about that link being referred to and by whom.</p>\n<p>This makes comments straightforward to manage, while also keeping the web do it’s original purpose of connecting to each other.</p>\n<h2 id=\"implementing-webmention\">Implementing Webmention</h2>\n<p>Implementing the Webmention is simple, I initially used the code for adding comments, and also <a href=\"https://codeberg.org/codingotaku/website/src/commit/84fdb9bbfc13cb1d1852e790269cd4035be53311/src/routes/main_pages.rs#L451\">automatically verified the link</a>, which would’ve made my website part of a DDoS network. So I decided to remove the link verification part on a <a href=\"https://codeberg.org/codingotaku/website/commit/0b39943064de72fe40a498fbb063fcecfb8bcea6\">later commit</a>. I also removed the comments in that same commit after thinking about the problems I described in the previous section.</p>\n<p>This means that my current implementation for receiving Webmention is as simple as validating the URL format and storing it in a database. I have a dedicated admin page for manually verifying it and adding it to my posts. I have a script that extracts the mention from the links I save, but I have not opened it up for the public yet (it will be done in a few weeks, so stay tuned).</p>\n<h2 id=\"what-next\">What next?</h2>\n<p>I have not implemented sending Webmention from my website, I just use a <code>curl</code> command for it currently, as it is just sending a <code>POST</code> request with the source and target links.</p>\n<pre><code># curl command to send webmention\ncurl -si protocol://the-website.tld/webmention-endpoint \\\n  -d source=protocol://my-website.tld/my-mention \\\n  -d target=protocol://the-website.tld/the-page-being-mentioned\n</code></pre>\n<p>Ideally, I would have a way to crawl through my pages, extract all external links I have in it, and send a Webmention to them if I have not done that already. It’s a lot of work, and I want to integrate it to my Webmention verification script so that it can be used by everyone. I am a bit preoccupied to do that at the moment.</p>\n<p>Supporting <a href=\"https://indieweb.org/IndieAuth\">IndieAuth</a> is the next one on my list, I already have a way to log in to this website with a password, IndieAuth would be a wrapper around it to redirect to a URL and generate a token. If I don’t end up redesigning this website, IndieAuth would be the thing you will see in my next blog post.</p>\n","content_text":"I stopped accepting comments in favour of [Webmentions](https://www.w3.org/TR/2017/REC-Webmention-20170112/) a while back. This was not a decision we made lightly, especially because I put a lot of time and energy in adding [comment support recently](/blogs/adding-comments).\n\n## Why remove comments?\n\nWhile I was able to fully moderate the comments and reduced the spams to near zero, I also need to be aware of the privacy concerns of the people commenting on my website.\n\nI tackled this by letting people comment anonymously, but I still allow them to share their name, email, etc., if they like.\n\nThere are some problems with this approach:\n\n**Validity:**\n:   I have no way to ensure that the person commenting on my website is really the person whom they are claiming to be.\n\n**Spams and trolls:**\n:   While I have managed to prevent all automated spams, people can manually troll my website, adding a gate like moderation to block this behaviour helps, but is still not good for my mental health.\n\n**Interactivity:**\n:   People usually comment and expect a response, but my previous system was treating it like shouting at the void, so it's not a good user experience.\n\n**Visibility:**\n:   If someone comments or mentions my website on *their* website, that is also a comment, there is no way for me to track it unless they enter their whole comment again on my website.\n\n## Why Webmention?\n\nSome of you already know this, [I partially support IndieWeb](/am-i-indieweb-yet) on this website. One of the major things that I was missing for in it was Webmentions. Unlike supporting comments, Webmentions makes it easy to filter out things that I don't want and works as a better gatekeeper than manual moderation (though I still moderate the Webmentions I receive).\n\nWebmention is a W3 standard to let websites talk to each other, it could be a comment, like, a simple reference, or whatever. The idea is that, if \"Website A\" has a link that mentions \"Website B\", the \"Website A\" or someone else can tell \"Website B\" about that link being referred to and by whom.\n\nThis makes comments straightforward to manage, while also keeping the web do it's original purpose of connecting to each other.\n\n## Implementing Webmention\n\nImplementing the Webmention is simple, I initially used the code for adding comments, and also [automatically verified the link](https://codeberg.org/codingotaku/website/src/commit/84fdb9bbfc13cb1d1852e790269cd4035be53311/src/routes/main_pages.rs#L451), which would've made my website part of a DDoS network. So I decided to remove the link verification part on a [later commit](https://codeberg.org/codingotaku/website/commit/0b39943064de72fe40a498fbb063fcecfb8bcea6). I also removed the comments in that same commit after thinking about the problems I described in the previous section.\n\nThis means that my current implementation for receiving Webmention is as simple as validating the URL format and storing it in a database. I have a dedicated admin page for manually verifying it and adding it to my posts. I have a script that extracts the mention from the links I save, but I have not opened it up for the public yet (it will be done in a few weeks, so stay tuned).\n\n## What next?\n\nI have not implemented sending Webmention from my website, I just use a `curl` command for it currently, as it is just sending a `POST` request with the source and target links.\n\n```\n# curl command to send webmention\ncurl -si protocol://the-website.tld/webmention-endpoint \\\n  -d source=protocol://my-website.tld/my-mention \\\n  -d target=protocol://the-website.tld/the-page-being-mentioned\n```\n\nIdeally, I would have a way to crawl through my pages, extract all external links I have in it, and send a Webmention to them if I have not done that already. It's a lot of work, and I want to integrate it to my Webmention verification script so that it can be used by everyone. I am a bit preoccupied to do that at the moment.\n\nSupporting [IndieAuth](https://indieweb.org/IndieAuth) is the next one on my list, I already have a way to log in to this website with a password, IndieAuth would be a wrapper around it to redirect to a URL and generate a token. If I don't end up redesigning this website, IndieAuth would be the thing you will see in my next blog post.","summary":"After thinking about it a long time, I have replaced the comments section on this website with Webmentions.","date_published":"2024-08-08T17:00:37Z","authors":[{"name":"Coding Otaku","url":"https://codingotaku.com/users/otaku"}]},{"id":"https://codingotaku.com/blogs/adding-comments","url":"https://codingotaku.com/blogs/adding-comments","title":"Adding Comments to My Website","content_html":"<p><strong>Note:</strong> I <a href=\"/blogs/adding-webmentions\">stopped accepting comments</a> in favour of webmentions, this post is here for archiving purposes.</p>\n<p>My <a href=\"/projects/website\">website</a> is written in <code>Rust</code> with <code>Rocket</code> framework. But, just like any other website, what language or framework I use doesn’t matter here. The source code can be found at my <a href=\"https://codeberg.org/codingotaku/website/\">codeberg repository</a>.</p>\n<h2 id=\"using-sqlite-for-database\">Using SQLite for database</h2>\n<p>This was a hard one to choose, I have many things planned for this website, most of which would require a high performing database. But I ended up using <code>SQLite</code> as it is easy to maintain and work on. Taking backups is done by a simple <code>rsync</code> command!</p>\n<p>I created a table named <code>comments</code> and I set all columns as text, including the ID as I will be using <a href=\"https://datatracker.ietf.org/doc/html/rfc4122\">UUID</a> (Universally Unique IDentifier) for it. The SQL looked something like this:</p>\n<pre><code>CREATE TABLE IF NOT EXISTS comments (\n  id TEXT NOT NULL, -- random UUID\n  post TEXT NOT NULL, -- URL for the page\n  name TEXT NOT NULL, -- Optional name, defaulted to anonymous\n  email TEXT, -- optional email\n  website TEXT, -- optional website for the commenter\n  comment TEXT NOT NULL, -- the comment\n  commenter TEXT NOT NULL, -- random UUID\n  reply_to TEXT -- something for the future threaded replies\n)\n</code></pre>\n<p>You might have noticed that I did not tell SQLite to set the default value for the <code>name</code> field with <code>name TEXT DEFAULT 'anonymous'</code>. This is because of a bug I faced in the rust SQLite library I am using, the library was not converting <code>Option::None</code> as <code>NULL</code> in SQLite. So instead, I am <a href=\"https://codeberg.org/codingotaku/website/src/commit/28c9ffa434f44e20a10870feb41672ba6c818f80/src/modals/comments.rs#L131\">programmatically setting</a> the default value for now.</p>\n<p>The comments are sent as a <a href=\"https://codeberg.org/codingotaku/website/src/commit/28c9ffa434f44e20a10870feb41672ba6c818f80/src/routes/main_pages.rs#L282\">post request</a>, I do some request guards there to avoid spam, more on that in the next section.</p>\n<h2 id=\"spam-control\">Spam control</h2>\n<p>One of the major concerns I have had about implementing a comment system was the spam control. I believe that I have very low traffic for this website as I don’t have <a href=\"/blogs/why-do-i-not-use-analytics\">analytics to confirm</a>, and I <a href=\"/privacy\">delete access logs fairly frequently</a>.</p>\n<p>I initially just accepted all the comments. Well, you could probably guess what happened, a low traffic website means that there are more bots crawling my site than humans visiting it. I frequently received spam messages, most containing links to some pharmacy in India, and some were just trolls. The links were easy to handle, I just show plain text in comments, so none of the links were rendered as HTML to avoid the visitors and search engines navigating to it.</p>\n<p>My first plan for moderation was to send a push notification to my android phone whenever a comment was added to the website so that I can delete it quickly. I wanted to do it using <a href=\"https://unifiedpush.org/\">UnifiedPush</a> as I already have <a href=\"https://codeberg.org/NextPush/uppush\">NextPush</a> server hosted in my <a href=\"https://nextcloud.com/\">Nextcloud</a> instance. Don’t think that it is too much work, if you have a notification server set-up, it is just a matter of sending a <code>POST</code> request <a href=\"https://codeberg.org/codingotaku/website/src/commit/d2ecf2a51e2fbd1f10cd72014f4bd0388caac0d9/src/routes/main_pages.rs#L310\">like I did</a>. If you do not have a server to set one up, something like <a href=\"https://ntfy.sh/\">ntfy.sh</a> might interest you.</p>\n<p>Sending myself a notification is obviously not a good way to do moderation, but it worked for me, as the frequency of spams reduced as I started deleting them immediately.</p>\n<p>Just because the spams reduced doesn’t mean that I did good moderation, I was still getting spams at least once a week. It was then I decided to ask a question to the commenter, it’s a simple one to answer if they are human. And even if the answer is wrong, I return an HTTP <a href=\"https://codeberg.org/codingotaku/website/src/commit/d2ecf2a51e2fbd1f10cd72014f4bd0388caac0d9/src/routes/main_pages.rs#L290\">redirect response</a>, this will trick the bots into thinking that they succeeded. I went with this approach as I would rather not implement captcha, there are no good accessibility friendly captchas out there!</p>\n<p>I never received a spam since then, I probably have missed a few real comments if the visitor couldn’t answer the question. It can be solved by adding the wrong answers to a separate table or something and moderating it later. But I don’t think it is that important for my small and humble website.</p>\n<p>In the future, I want the visitors to be able to reply to comments. I already have some work done for that in the backend, it will probably be released along with the <a href=\"https://indieweb.org/Webmention\">webmention</a> support I am working on at the moment!</p>\n<p>The posted comments will be hidden until I review and <a href=\"https://codeberg.org/codingotaku/website/src/commit/28c9ffa434f44e20a10870feb41672ba6c818f80/src/routes/admin.rs#L189\">approve it</a>. But it would be a problem for the commenter if they can’t see their own comment. So, for a slightly better user experience, the commenters can now see their comments even if it is not moderated yet, they can also edit or delete it if they’d like to.</p>\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/07682cac-0472-436f-8d92-70097bc85be5.webp\" alt=\"\" width=\"817\" height=\"443\"><figcaption>Screenshot of a comment waiting for moderation, with links to edit and delete it.</figcaption></figure>\n<p>This is done by storing <a href=\"https://codeberg.org/codingotaku/website/src/commit/28c9ffa434f44e20a10870feb41672ba6c818f80/src/routes/main_pages.rs#L294\">a unique ID</a> in a <a href=\"https://codeberg.org/codingotaku/website/src/commit/28c9ffa434f44e20a10870feb41672ba6c818f80/src/routes/main_pages.rs#L300\">private cookie</a> when someone comments for the first time and using it to connect to all their comments from then onwards. The ID is a randomly generated UUID.</p>\n<p>My final SQL for the comment database looks like this:</p>\n<pre><code>CREATE TABLE IF NOT EXISTS comments (\n  id TEXT NOT NULL, -- random UUID\n  post TEXT NOT NULL, -- URL for the page\n  name TEXT NOT NULL, -- Optional name, defaulted to anonymous\n  email TEXT, -- optional email\n  website TEXT, -- optional website for the commenter\n  comment TEXT NOT NULL, -- the comment\n  commenter TEXT NOT NULL, -- random UUID\n  reply_to TEXT, -- something for the future threaded replies\n  date TEXT NOT NULL -- the created date, I could use the DATE type here though\n  is_moderated INTEGER NOT NULL, -- Integer because SQLite doesn't support boolean.\n                                 -- 1 means that the comment is moderated.\n)\n</code></pre>\n<p>For now, I think this is sufficient for my needs, even providing comments is already an overkill for my small website.</p>\n","content_text":"**Note:** I [stopped accepting comments](/blogs/adding-webmentions) in favour of webmentions, this post is here for archiving purposes.\n\nMy [website](/projects/website) is written in `Rust` with `Rocket` framework. But, just like any other website, what language or framework I use doesn't matter here. The source code can be found at my [codeberg repository](https://codeberg.org/codingotaku/website/).\n\n## Using SQLite for database\n\nThis was a hard one to choose, I have many things planned for this website, most of which would require a high performing database. But I ended up using `SQLite` as it is easy to maintain and work on. Taking backups is done by a simple `rsync` command!\n\nI created a table named `comments` and I set all columns as text, including the ID as I will be using [UUID](https://datatracker.ietf.org/doc/html/rfc4122) (Universally Unique IDentifier) for it. The SQL looked something like this:\n\n```\nCREATE TABLE IF NOT EXISTS comments (\n  id TEXT NOT NULL, -- random UUID\n  post TEXT NOT NULL, -- URL for the page\n  name TEXT NOT NULL, -- Optional name, defaulted to anonymous\n  email TEXT, -- optional email\n  website TEXT, -- optional website for the commenter\n  comment TEXT NOT NULL, -- the comment\n  commenter TEXT NOT NULL, -- random UUID\n  reply_to TEXT -- something for the future threaded replies\n)\n```\n\nYou might have noticed that I did not tell SQLite to set the default value for the `name` field with `name TEXT DEFAULT 'anonymous'`. This is because of a bug I faced in the rust SQLite library I am using, the library was not converting `Option::None` as `NULL` in SQLite. So instead, I am [programmatically setting](https://codeberg.org/codingotaku/website/src/commit/28c9ffa434f44e20a10870feb41672ba6c818f80/src/modals/comments.rs#L131) the default value for now.\n\nThe comments are sent as a [post request](https://codeberg.org/codingotaku/website/src/commit/28c9ffa434f44e20a10870feb41672ba6c818f80/src/routes/main_pages.rs#L282), I do some request guards there to avoid spam, more on that in the next section.\n\n## Spam control\n\nOne of the major concerns I have had about implementing a comment system was the spam control. I believe that I have very low traffic for this website as I don't have [analytics to confirm](/blogs/why-do-i-not-use-analytics), and I [delete access logs fairly frequently](/privacy).\n\nI initially just accepted all the comments. Well, you could probably guess what happened, a low traffic website means that there are more bots crawling my site than humans visiting it. I frequently received spam messages, most containing links to some pharmacy in India, and some were just trolls. The links were easy to handle, I just show plain text in comments, so none of the links were rendered as HTML to avoid the visitors and search engines navigating to it.\n\nMy first plan for moderation was to send a push notification to my android phone whenever a comment was added to the website so that I can delete it quickly. I wanted to do it using [UnifiedPush](https://unifiedpush.org/) as I already have [NextPush](https://codeberg.org/NextPush/uppush) server hosted in my [Nextcloud](https://nextcloud.com/) instance. Don't think that it is too much work, if you have a notification server set-up, it is just a matter of sending a `POST` request [like I did](https://codeberg.org/codingotaku/website/src/commit/d2ecf2a51e2fbd1f10cd72014f4bd0388caac0d9/src/routes/main_pages.rs#L310). If you do not have a server to set one up, something like [ntfy.sh](https://ntfy.sh/) might interest you.\n\nSending myself a notification is obviously not a good way to do moderation, but it worked for me, as the frequency of spams reduced as I started deleting them immediately.\n\nJust because the spams reduced doesn't mean that I did good moderation, I was still getting spams at least once a week. It was then I decided to ask a question to the commenter, it's a simple one to answer if they are human. And even if the answer is wrong, I return an HTTP [redirect response](https://codeberg.org/codingotaku/website/src/commit/d2ecf2a51e2fbd1f10cd72014f4bd0388caac0d9/src/routes/main_pages.rs#L290), this will trick the bots into thinking that they succeeded. I went with this approach as I would rather not implement captcha, there are no good accessibility friendly captchas out there!\n\nI never received a spam since then, I probably have missed a few real comments if the visitor couldn't answer the question. It can be solved by adding the wrong answers to a separate table or something and moderating it later. But I don't think it is that important for my small and humble website.\n\nIn the future, I want the visitors to be able to reply to comments. I already have some work done for that in the backend, it will probably be released along with the [webmention](https://indieweb.org/Webmention) support I am working on at the moment!\n\nThe posted comments will be hidden until I review and [approve it](https://codeberg.org/codingotaku/website/src/commit/28c9ffa434f44e20a10870feb41672ba6c818f80/src/routes/admin.rs#L189). But it would be a problem for the commenter if they can't see their own comment. So, for a slightly better user experience, the commenters can now see their comments even if it is not moderated yet, they can also edit or delete it if they'd like to.\n\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/07682cac-0472-436f-8d92-70097bc85be5.webp\" alt=\"\" width=\"817\" height=\"443\"><figcaption>Screenshot of a comment waiting for moderation, with links to edit and delete it.</figcaption></figure>\n\nThis is done by storing [a unique ID](https://codeberg.org/codingotaku/website/src/commit/28c9ffa434f44e20a10870feb41672ba6c818f80/src/routes/main_pages.rs#L294) in a [private cookie](https://codeberg.org/codingotaku/website/src/commit/28c9ffa434f44e20a10870feb41672ba6c818f80/src/routes/main_pages.rs#L300) when someone comments for the first time and using it to connect to all their comments from then onwards. The ID is a randomly generated UUID.\n\nMy final SQL for the comment database looks like this:\n\n```\nCREATE TABLE IF NOT EXISTS comments (\n  id TEXT NOT NULL, -- random UUID\n  post TEXT NOT NULL, -- URL for the page\n  name TEXT NOT NULL, -- Optional name, defaulted to anonymous\n  email TEXT, -- optional email\n  website TEXT, -- optional website for the commenter\n  comment TEXT NOT NULL, -- the comment\n  commenter TEXT NOT NULL, -- random UUID\n  reply_to TEXT, -- something for the future threaded replies\n  date TEXT NOT NULL -- the created date, I could use the DATE type here though\n  is_moderated INTEGER NOT NULL, -- Integer because SQLite doesn't support boolean.\n                                 -- 1 means that the comment is moderated.\n)\n```\n\nFor now, I think this is sufficient for my needs, even providing comments is already an overkill for my small website.","summary":"I have had comments in my website for some time, this post is about how I did it and the improvements I made for better UX.","date_published":"2024-05-06T17:20:23Z","date_modified":"2024-07-07T22:14:10Z","authors":[{"name":"Coding Otaku","url":"https://codingotaku.com/users/otaku"}]},{"id":"https://codingotaku.com/blogs/writing-a-website-part-2:-html-and-css","url":"https://codingotaku.com/blogs/writing-a-website-part-2:-html-and-css","title":"Writing a Website part 2: HTML and CSS","content_html":"<p>This is part two of the series on writing a website, in the part one, <a href=\"/blogs/writing-a-website-part-1\">“Recipe for Making a Website”</a>, I wrote about <strong><em>what</em> we can use</strong> to write a website, <strong>but not <em>how</em> to write one</strong>.</p>\n<p>The images in here might be unreadable when reading on devices with small screen size. This is because the images are screenshots taken from a laptop screen and resized, I have provided sufficient (or I believe they are sufficient) descriptions for them. Ideally, you should be able to get the same results in your system if you do the steps I mention in this article. If you still need to see the images, please open them in a new tab.</p>\n<p>A website is usually created to provide information to a visitor. Which is typically written in text format. There are many ways to send this information to the visitor, but let us not focus on that for now, what we need is to write that information down somehow so that it can be shared.</p>\n<p>But wait, shouldn’t we figure out <em>how to share the information</em> first so that we can write it down in a way that it can be shared?</p>\n<p>It might seem like a chicken-and-egg problem, but fortunately for us, this is already solved. There is an existing and widely used protocol named HTTP (Hypertext Transfer Protocol) that supports transferring information through the internet, and that protocol supports a markup language called HTML, which is what we are going to use today.</p>\n<p>HyperText Markup Language or HTML is the standard markup language for documents designed to be displayed in a web browser. It defines the content and structure of web content.</p>\n<p>HTML is a complex and evolving markup language, but it is easy to use because there are very few rules you need to learn to write a full-fledged website.</p>\n<h2 id=\"structuring-the-web-page\">Structuring the web page</h2>\n<p>In HTML, the structure is defined using “tags”, a tag in HTML is usually written in the format <code>&lt;tag-name&gt;</code> this is called an opening tag. An opened tag can be closed by writing the same tag and adding a <code>/</code> after <code>&lt;</code> (e.g. <code>&lt;/tag-name&gt;</code>).</p>\n<p>An “element”, or to better word it, an “HTML element”, <em>often</em> consists of opening and closing tags. Everything in between those tags are called “content” of that element. For example, <code>&lt;q&gt;flower&lt;/q&gt;</code> creates a <code>quote</code> element, and it is rendered as <code>\"flower\"</code> — here “flower” is the content.</p>\n<p>You can place an <em>element</em> within another <em>element</em>, this is called “nesting”. But what we call it is not relevant, all you need to know is that you can infinitely nest elements! But be careful, the more nested the elements are, the more you burden the visitor’s browser and their RAM, so we must try to reduce nesting as much as possible.</p>\n<p>Some elements cannot have any child (nested) element or text contents, they are called “void” elements. Void elements only have a start tag; end tags must not be specified for void elements. An example of this is the <code>&lt;input&gt;</code> tag.</p>\n<p>Some HTML formatting tools (like prettier) try to “self-close” void tags by inserting a trailing <code>/</code> after the tag name (<code>&lt;input/&gt;</code>), there are some historical reasons for that, like supporting XML parsers where there are no concepts of “void” tags.</p>\n<p>Fun fact, no browser will complain or behave weirdly if you self-close a void tag (citation needed). But self-closing is <strong>not</strong> a concept in HTML, so any tool trying to insert a self-closing tag in HTML to support XML parsers is fighting with two things that do not support each other.</p>\n<p>Though no error will be thrown, there are some more restrictions on what elements can be placed in another, but let us not worry about that for now and learn different elements that we can use.</p>\n<h3 id=\"scemantic-elements\">Scemantic elements</h3>\n<p>Most elements convey semantic meaning, and purpose. A <code>&lt;p&gt;</code> tag is used to create a paragraph element, for example. This paragraph is written in HTML, like this.</p>\n<pre><code>&lt;p&gt;\n  Most elements convey semantic meaning, and purpose. A &lt;code&gt;&lt;p&gt;&lt;/code&gt; tag is used to create a paragraph element, for example. This paragraph is written in HTML, like this.\n&lt;/p&gt;\n</code></pre>\n<p>Noticed the weird <code>&lt;p&gt;</code>? In HTML, if you want to write <code>&lt;</code> or <code>&gt;</code> as text, you need to escape them. This can be done by writing <code>&lt;</code> and <code>&gt;</code>. So <code>&lt;p&gt;</code> will create the text <code>&lt;p&gt;</code> instead of creating a tag. Modern IDEs will probably highlight them so that it will be easier to read.</p>\n<p>There are many more characters that you can escape like this, you might be interested in reading about <a href=\"https://www.w3.org/International/questions/qa-escapes\">using character escapes in markup and CSS</a>.</p>\n<p>A good website with lots of content will have more paragraph elements than any other elements, but showing just plain text would be a bit boring and might not suit everyone’s needs or taste. There are elements to style texts within a paragraph, and some of these do similar things, but you can use them based on their semantic meaning.</p>\n<p>For example, both <code>&lt;i&gt;</code> and <code>&lt;em&gt;</code> tags are rendered (displayed) by the browser by making its content italic.\nScemantically, <code>&lt;i&gt;</code> can be used to make a text <em>italic</em> in style, but to <em>emphasize</em> a text, we should use <code>&lt;em&gt;</code> tag instead.</p>\n<p>Other similarly styled tags are <code>&lt;b&gt;</code> and <code>&lt;strong&gt;</code>, <strong>while both of them render the text in bold</strong>, <code>&lt;strong&gt;</code> tag is used to show strong text that has a “strong importance”. And <code>&lt;b&gt;</code> tag is for, as you guessed, to make the text bold (technically, increasing the font weight).</p>\n<p>While paragraphs are important, it is also important to have heading and subheading for the document, this makes it easier for the reader to traverse through the document.\n<code>&lt;h1&gt;</code> tag is used to show the heading of the document, a document must have <strong>only one</strong> of them (though as usual, the browser won’t stop you from having more).</p>\n<p>You can create a subheading by increasing the number after the “h” in the heading tag, at present, it is possible to have subheadings from <code>h2 to h6</code>. When using them, one must not skip a heading level, i.e a <code>h3</code> must not be created if there is no <code>h2</code> tag present before it. The higher the number, the smaller the text, but as I already mentioned, it is used to write headings and subheadings, and <em>not</em> to style texts.</p>\n<h3 id=\"writing-an-html-document\">Writing an HTML document</h3>\n<p>Now that we learned <em>how</em> to create text content, let us focus on putting this into a document and see it from a browser.</p>\n<p>As I mentioned at the beginning, the HTML is an evolving language, currently <code>HTML5</code> is widely used, it has support for more semantic HTML elements than its prior versions. The browser must know the document format and version that we are using, this is done by using the <code>&lt;!DOCTYPE&gt;</code> declaration.\nDid you notice that I wrote “declaration” and not “tag”? This is because the declaration is not an HTML tag. It is “information” to the browser about what document type to expect.</p>\n<p>Okay, now that we know what to do, let us create this document for real this time.</p>\n<p>To create an HTML document, any text editing software can be used. BUT, we are going to write plain text here, so it is better if you don’t use a word processor. Use a simple text editor, like ed, vi, vim, Emacs, Atom, Notepad++, or in worst-case use Sublime Text, or VS Code. But if you have opened Microsoft Word, close it, we don’t need it here.</p>\n<p>Create a new folder in your system — name it whatever you feel like, it is not relevant — now create an empty file within that folder named <code>index.html</code>, this time, the name is important, make sure you spelled it correctly.</p>\n<p>The name <code>index.html</code> is usually looked up by most tools that can be used as a server (homework, find the historical reasoning behind it). Though we are not using any such tools at this point, we will need it in the future, so let us stick to that naming convention for now.</p>\n<p>After creating the empty file, open it using your text editor and write the DOCTYPE to it. For HTML5, the DOCTYPE is written as <code>&lt;!DOCTYPE html&gt;</code> (yes, it is not a typo, it is <code>html</code> and not <code>html5</code>).</p>\n<p>The HTML document has a few tags that need to be placed to be rendered properly. The first one is <code>&lt;html&gt;</code>, this is the root element of the HTML document, and there should be only one root element present in an HTML document.</p>\n<p>Your file should now look like this:</p>\n<pre><code>&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n&lt;/html&gt;\n</code></pre>\n<p>Inside the <code>&lt;html&gt;</code> element, we can place two elements, one is <code>&lt;head&gt;</code> which can contain some additional information about the file, like the title of the document, information about the author, a brief description, how the document should be rendered, etc. For now, let us just add a title.</p>\n<pre><code>&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n  &lt;head&gt;\n    &lt;title&gt;A simple HTML document&lt;/title&gt;\n  &lt;/head&gt;\n&lt;/html&gt;\n</code></pre>\n<p>Simple? I will add two more things.</p>\n<p>To support more languages, we can tell the browser to use the <code>UTF-8</code> character set when rendering the text by placing this <code>meta</code> tag within the <code>head</code> element:\n<code>&lt;meta charset=\"UTF-8\"&gt;</code>.</p>\n<p>To tell what language the webpage is using to the browser, and any assistive technology the visitor uses, we can use the <code>lang</code> attribute in the <code>html</code> tag:\n<code>&lt;html lang=\"en\"&gt;</code>.</p>\n<p>Just like my website, I am using the International English here, for US english, you will need to use <code>en-us</code> instead. To find the correct code for the language you are using, try the <a href=\"https://r12a.github.io/app-subtags/\">Language Subtag Lookup</a>. <strong>You must do this because an empty lang tag means that the language is <code>undefined</code></strong>.</p>\n<p>The other element we can place inside the <code>&lt;html&gt;</code> is the <code>&lt;body&gt;</code> element, all contents that we write should be placed within the <code>body</code>.</p>\n<pre><code>&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n  &lt;head&gt;\n    &lt;meta charset=\"UTF-8\"&gt;\n    &lt;title&gt;A simple HTML document&lt;/title&gt;\n  &lt;/head&gt;\n  &lt;body&gt;\n  &lt;/body&gt;\n&lt;/html&gt;\n</code></pre>\n<p>While we can place all the contents within the body, it is a good practice to further divide the sections. For someone using assistive technology, it would be nice to have indication of different <strong>sections</strong> of the page, we call them <strong>landmarks</strong>. We are not going to use them now, just keep it in your mind, by the end of this series you will learn when and how to use them.</p>\n<p>What you have now created is called an “HTML boilerplate”, you can copy that file content and when creating more HTML files. Some IDEs (Integrated Development Environments) can generate this (and a bit more) for you, it is up-to you to figure that out.</p>\n<p>Now that the boilerplate is ready, let us put some content in it after remembering the rules:</p>\n<ul>\n<li>A document must have only one <code>h1</code> tag.</li>\n<li>Subheadings must not skip levels.</li>\n<li>All paragraphs must use <code>p</code> tag.</li>\n<li>Ensure that the document follows a semantic structure.</li>\n</ul>\n<pre><code>&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n  &lt;head&gt;\n    &lt;meta charset=\"UTF-8\"&gt;\n    &lt;title&gt;A simple HTML document&lt;/title&gt;\n  &lt;/head&gt;\n  &lt;body&gt;\n    &lt;h1&gt;Welcome to my simple HTML page&lt;/h1&gt;\n    &lt;p&gt;\n      An HTML page is used for sharing information with others. And to ensure that &lt;em&gt;everyone&lt;/em&gt; can access the page, we must be conscious of the &lt;strong&gt;semantic&lt;/strong&gt; and &lt;strong&gt;accessibility&lt;/strong&gt; when writing it.\n    &lt;/p&gt;\n  &lt;/body&gt;\n&lt;/html&gt;\n</code></pre>\n<p>Now save the file with the above content, once saved, you can open the <code>index.html</code> file using your web browser. There are many ways to do this, one way is to just drag the <code>index.html</code> file into the web browser. Or you might be able to right-click on the file and open it using the default browser (this flow varies between OS and Desktop environments).</p>\n<p>Once you have opened the file in the browser, you might see something like this screenshot.</p>\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/1bbd4245-ef8b-4425-be5c-b7b68f602a06.png\" alt=\"Firefox screenshot\" width=\"1052\" height=\"437\"><figcaption>Firefox tab withA simple HTML documentas title. The webpage contains black text in white background. The heading saysWelcome to my simple HTML page, and it is rendered as a big and heavy text.The paragraph below the heading has the texteveryonerendered in italic style. The textssemanticandaccessibilityare rendered in bold letteres.</figcaption></figure>\n<h2 id=\"styling-the-web-page\">Styling the web page</h2>\n<p>That screenshot above was very plain looking, and if you are reading my website in one of its dark themes, you <em>might</em> have strained your eyes, so how about we make it a bit better?</p>\n<p>Unlike the structure of the website, styling is not a compulsory thing to do. But just like the structure, one should be mindful of the semantic and accessibility of the page when styling an HTML page.</p>\n<p>One of the main things that many still get wrong is the colour contrast, the default colour scheme of your website must have proper contrast. Remember, if you create a public website, or share links to it, you are effectively asking others to read it, it should not be a burden for them.</p>\n<p>Fixing colour contrast used to be a hard thing to do, but now there are many tools that can help one to achieve good colour contrast. One of them is <a href=\"https://webaim.org/resources/contrastchecker/\">WebAIM: Contrast Checker</a>.</p>\n<p>Now that we have decided to be inclusive of people with needs, let us learn how to style a web page!</p>\n<p>There are multiple ways to style elements in HTML:</p>\n<ul>\n<li>Using inline styles by using the <code>style</code> <em>attribute</em></li>\n<li>Using the <code>&lt;style&gt;</code> <em>tag</em> inside <code>&lt;head&gt;</code> <em>tag</em>.</li>\n<li>Using a stylesheet, which is just the contents of the <code>&lt;style&gt;</code> placed in a separate file.</li>\n</ul>\n<p>We will be using a stylesheet, which is easier to read, write, and manage. The other ways I mentioned to style HTML documents are usually discouraged as it makes managing styles hard, but feel free to explore how to do them on your own.</p>\n<h3 id=\"stylesheets\">Stylesheets</h3>\n<p>A stylesheet is a simple text file that contain styles in CSS (Cascading Style Sheet) format. The purpose of CSS is to change the look and feel of the web page, but as you learn more about it, you might get tempted to ignore the HTML structure completly, we will try to avoid that here.</p>\n<p>CSS is not just for styling HTML, it can be used to style SVG or XML documents, and some graphical user interface toolkits also supports styling via CSS, so it is always good to learn a bit about it.</p>\n<p>For styles to work, we need to to tell the browser where to look for it. This is done by adding a <code>link</code> tag within the <code>head</code>. The syntax will look like <code>&lt;link rel=\"stylesheet\" href=\"/path/to/style.css\"&gt;</code>. As you might have noticed, <code>link</code> is a void tag, you do not need to close it.</p>\n<h4 id=\"the-syntax\">The syntax</h4>\n<p>The CSS has a very simple syntax, a <strong>selector</strong>, <strong>declaration block</strong>, <strong>properties</strong>, and <strong>values</strong>. There are also some more advanced things like <strong>media queries</strong>, but the basics remains the same.</p>\n<p>If you decide to have the main heading on your page to be shown in the center with red text, the following code shows a very simple CSS rule that would achieve that styling:</p>\n<pre><code>h1 {\n  color: red;\n  text-align: center;\n}\n</code></pre>\n<ul>\n<li>In the above example, the CSS rule opens with a selector. This selects the HTML element that we are going to style. In this case, we are styling level one headings (<code>h1</code>).</li>\n<li>We then have a set of curly braces <code>{ }</code>. This is the declaration block.</li>\n<li>Inside the braces will be one or more <strong>declarations</strong>, which take the form of <strong>property</strong> and <strong>value</strong> pairs. We specify the property (<code>color</code> in the above example) before the colon, and we specify the value of the property after the colon (<code>red</code> in this example).</li>\n<li>This example contains two declarations, one for <code>color</code> and the other for <code>text-align</code>. Each pair specifies a property of the element(s) we are selecting (<code>h1</code> in this case), then a value that we’d like to give to the property.</li>\n</ul>\n<p>Let us create a simple stylesheet first named <strong>style.css</strong> with the above example. We will create it in the same folder you placed the <strong>index.html</strong> file to.</p>\n<p>Now that we have a simple css file, add it inside the <code>head</code> tag witin the <strong>index.html</strong> file.</p>\n<pre><code>&lt;link rel=\"stylesheet\" href=\"./style.css\"&gt;\n</code></pre>\n<p>You will notice that the <code>href</code> is pointing to <code>./style.css</code>. The <code>\".\"</code> here is to tell that the path is <strong>relative to the index.html</strong> file, and you will probably never see it on a real website because there are better ways to do this when hosting it. I will talk about that later in this blog, let us focus on the style for now.</p>\n<p>Your <strong>index.html</strong> file should now look like this.</p>\n<pre><code>&lt;html lang=\"en\"&gt;\n  &lt;head&gt;\n    &lt;meta charset=\"UTF-8\"&gt;\n    &lt;title&gt;A simple HTML document&lt;/title&gt;\n    &lt;link rel=\"stylesheet\" href=\"./style.css\"&gt;\n  &lt;/head&gt;\n  &lt;body&gt;\n    &lt;h1&gt;Welcome to my simple HTML page&lt;/h1&gt;\n    &lt;p&gt;\n      An HTML page is used for sharing information with others. And to ensure that &lt;em&gt;everyone&lt;/em&gt; can access the page, we must be conscious of the &lt;strong&gt;semantic&lt;/strong&gt; and &lt;strong&gt;accessibility&lt;/strong&gt; when writing it.\n    &lt;/p&gt;\n  &lt;/body&gt;\n&lt;/html&gt;\n</code></pre>\n<p>Once you save those files and refresh your browser, the content remains the same, but you will see new the style like below.</p>\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/ab2bd8b3-2e24-40b8-9c3d-762fa24cd4ce.png\" alt=\"Firefox screenshot\" width=\"1122\" height=\"347\"><figcaption>The headingWelcome to my simple HTML pageis now rendered as a big and heavy text in red. It is aligned to the center of the page.</figcaption></figure>\n<h4 id=\"understanding-selectors\">Understanding selectors</h4>\n<p>To identify which element to style, we use <code>CSS selectors</code>. This can be its <code>tag name</code>, <code>id</code>, <code>class</code>, or any other <code>attributes</code>.</p>\n<p>It is also possible to style an element based on <code>user action</code> like when a user <code>hover</code> over an element, or is curruntly <code>focusing</code> on it with a mouse or keyboard.</p>\n<p>The possiblities are endless, so to make a CSS stylesheet easy to read, and to reduce the burden on the browser which calculates how to style things, we should use simple selectors while also avoiding style conflicts.</p>\n<p>I am going to provide some examples of selectors here, it might feel a bit overwhelming, but just like HTML elements, you do not need to understand them all. You will gradually learn as you work with them.</p>\n<p>We have already seen how to style using an element’s tag name, we just write it’s tag name, i.e., to style <code>&lt;h1&gt;</code> element, we just write <code>h1</code> as its selector.</p>\n<pre><code>/*Styling all h1 elements */\nh1 {\n  /* some styles */\n}\n</code></pre>\n<p>The text written between <code>/*</code> &nbsp;and&nbsp; <code>*/</code> are not considered styles, they are the way to do comments in CSS, and the browser will ignore it.</p>\n<p>To style using an element’s attribute, we use an attribute selector, a simple attribute selector can be written as <code>[attribute-name=\"value\"]</code>.</p>\n<pre><code>/* Styling all elements with the title attribute set as \"cheese\" */\n[title=\"cheese\"] {\n  /* some styles */\n}\n</code></pre>\n<p>Two HTML attributes, <code>id</code>, and <code>class</code> are special, because they are used a lot for styling HTML.\nTo style using a <code>class</code>, we prefix the class name with a “.”, so the syntax will be <code>.class-name</code>.\nTo style using an element’s <code>id</code>, we prefix the class name with a “#”, so the syntax will be <code>#element-id</code>.</p>\n<pre><code>/* Styling all elements with the class \"cat\" */\n.cat {\n  /* some styles */\n}\n\n/* Styling element with the id \"mouse\" */\n#mouse {\n  /* some styles */\n}\n</code></pre>\n<p>It is also possible to join multiple selectors together, to do that, we just write them together <em><strong>without any space between them</strong></em>.</p>\n<pre><code>/* Styling all image elements with the class \"cat\" */\nimage.cat {\n  /* some styles */\n}\n</code></pre>\n<p>If you put a space between selectors, that means that the selector preceeding is a child element. for example, the selector <code>#house .mouse .cheese</code> will style the element with the class <code>cheese</code> which is under an element with the class <code>mouse</code> which is under another element with the id <code>house</code></p>\n<pre><code>/* Styling all elements with the class \"cat\" inside the element with id \"house\" */\n#house .cat {\n  /* some styles */\n}\n</code></pre>\n<p>Let us update the <strong>style.css</strong> file a bit, I’m setting a purple colour for the background, white text colour, removed the attention seeking red heading, and increased the text size for the paragraph.</p>\n<pre><code>body {\n  background-color: purple;\n  color: white;\n  line-height: 2em;\n}\n\nh1 {\n  text-align: center;\n}\n\np {\n  font-size: 1.2em;\n}\n</code></pre>\n<p>Let us refresh the page again to see new the style.</p>\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/e4004682-1fca-4c18-8119-33bf613f5653.png\" alt=\"Firefox screenshot with new changes\" width=\"1122\" height=\"347\"><figcaption>The background colour has now changed to purple, all the text, including the title is now in white color. There now a bit more gap between the lines in the paragraph, and its font size has increased sightly.</figcaption></figure>\n<p>Now we have a problem, the colours we set might not be readable under some light conditions, and some prefers light theme while others need a dark theme. Some set their system or the browser to automatically change the theme based on time or location. It is one of the very convenient ways to avoid eye strain.</p>\n<p>Well, it is not just the visitors, you might also prefer one theme over the other most of the time. While many debate about what is good for the eyes even now, switching between dark and light themes is an accessibility feature, and we are considerate to people with needs, right?</p>\n<p>Most modern browsers now support a few convenient ways to tackle this in pure CSS.</p>\n<p><strong>Media queries</strong>\n:   Media queries allow you to apply CSS styles depending on a device’s media type (such as print vs. screen) or other features or characteristics such as screen resolution or orientation, aspect ratio, browser viewport width or height, or user preferences. These may include preferences such as preferring reduced motion, data usage, or transparency.</p>\n<p><strong>CSS custom properties (CSS variables)</strong>\n:   CSS variables allow you to assign property values and dynamically change it. This is a powerful feature when combined with media queries or JavaScript (A programming language used to make websites more interactive).</p>\n<p><strong>color-scheme property</strong>\n:   The color-scheme CSS property allows an element to indicate which colour schemes it can comfortably be rendered in. This is intended to help the browser style the form controls like text boxes and buttons, and scrollbars.</p>\n<p>I will write more about Media queries and dynamic styling in the next part of this series.</p>\n","content_text":"This is part two of the series on writing a website, in the part one, [\"Recipe for Making a Website\"](/blogs/writing-a-website-part-1), I wrote about ***what* we can use** to write a website, **but not *how* to write one**.\r\n\r\nThe images in here might be unreadable when reading on devices with small screen size. This is because the images are screenshots taken from a laptop screen and resized, I have provided sufficient (or I believe they are sufficient) descriptions for them. Ideally, you should be able to get the same results in your system if you do the steps I mention in this article. If you still need to see the images, please open them in a new tab.\r\n\r\nA website is usually created to provide information to a visitor. Which is typically written in text format. There are many ways to send this information to the visitor, but let us not focus on that for now, what we need is to write that information down somehow so that it can be shared.\r\n\r\nBut wait, shouldn't we figure out *how to share the information* first so that we can write it down in a way that it can be shared?\r\n\r\nIt might seem like a chicken-and-egg problem, but fortunately for us, this is already solved. There is an existing and widely used protocol named HTTP (Hypertext Transfer Protocol) that supports transferring information through the internet, and that protocol supports a markup language called HTML, which is what we are going to use today.\r\n\r\nHyperText Markup Language or HTML is the standard markup language for documents designed to be displayed in a web browser. It defines the content and structure of web content.\r\n\r\nHTML is a complex and evolving markup language, but it is easy to use because there are very few rules you need to learn to write a full-fledged website.\r\n\r\n## Structuring the web page\r\n\r\nIn HTML, the structure is defined using \"tags\", a tag in HTML is usually written in the format `<tag-name>` this is called an opening tag. An opened tag can be closed by writing the same tag and adding a `/` after `<` (e.g. `</tag-name>`).\r\n\r\nAn \"element\", or to better word it, an \"HTML element\", *often* consists of opening and closing tags. Everything in between those tags are called \"content\" of that element. For example, `<q>flower</q>` creates a `quote` element, and it is rendered as `\"flower\"` — here \"flower\" is the content.\r\n\r\nYou can place an *element* within another *element*, this is called \"nesting\". But what we call it is not relevant, all you need to know is that you can infinitely nest elements! But be careful, the more nested the elements are, the more you burden the visitor's browser and their RAM, so we must try to reduce nesting as much as possible.\r\n\r\nSome elements cannot have any child (nested) element or text contents, they are called \"void\" elements. Void elements only have a start tag; end tags must not be specified for void elements. An example of this is the `<input>` tag.\r\n\r\nSome HTML formatting tools (like prettier) try to \"self-close\" void tags by inserting a trailing `/` after the tag name (`<input/>`), there are some historical reasons for that, like supporting XML parsers where there are no concepts of \"void\" tags.\r\n\r\nFun fact, no browser will complain or behave weirdly if you self-close a void tag (citation needed). But self-closing is **not** a concept in HTML, so any tool trying to insert a self-closing tag in HTML to support XML parsers is fighting with two things that do not support each other.\r\n\r\nThough no error will be thrown, there are some more restrictions on what elements can be placed in another, but let us not worry about that for now and learn different elements that we can use.\r\n\r\n### Scemantic elements\r\n\r\nMost elements convey semantic meaning, and purpose. A `<p>` tag is used to create a paragraph element, for example. This paragraph is written in HTML, like this.\r\n\r\n```\r\n<p>\r\n  Most elements convey semantic meaning, and purpose. A <code><p></code> tag is used to create a paragraph element, for example. This paragraph is written in HTML, like this.\r\n</p>\r\n```\r\n\r\nNoticed the weird `<p>`? In HTML, if you want to write `<` or `>` as text, you need to escape them. This can be done by writing `<` and `>`. So `<p>` will create the text `<p>` instead of creating a tag. Modern IDEs will probably highlight them so that it will be easier to read.\r\n\r\nThere are many more characters that you can escape like this, you might be interested in reading about [using character escapes in markup and CSS](https://www.w3.org/International/questions/qa-escapes).\r\n\r\nA good website with lots of content will have more paragraph elements than any other elements, but showing just plain text would be a bit boring and might not suit everyone's needs or taste. There are elements to style texts within a paragraph, and some of these do similar things, but you can use them based on their semantic meaning.\r\n\r\nFor example, both `<i>` and `<em>` tags are rendered (displayed) by the browser by making its content italic.\r\nScemantically, `<i>` can be used to make a text *italic* in style, but to *emphasize* a text, we should use `<em>` tag instead.\r\n\r\nOther similarly styled tags are `<b>` and `<strong>`, **while both of them render the text in bold**, `<strong>` tag is used to show strong text that has a \"strong importance\". And `<b>` tag is for, as you guessed, to make the text bold (technically, increasing the font weight).\r\n\r\nWhile paragraphs are important, it is also important to have heading and subheading for the document, this makes it easier for the reader to traverse through the document.\r\n`<h1>` tag is used to show the heading of the document, a document must have **only one** of them (though as usual, the browser won't stop you from having more).\r\n\r\nYou can create a subheading by increasing the number after the \"h\" in the heading tag, at present, it is possible to have subheadings from `h2 to h6`. When using them, one must not skip a heading level, i.e a `h3` must not be created if there is no `h2` tag present before it. The higher the number, the smaller the text, but as I already mentioned, it is used to write headings and subheadings, and *not* to style texts.\r\n\r\n### Writing an HTML document\r\n\r\nNow that we learned *how* to create text content, let us focus on putting this into a document and see it from a browser.\r\n\r\nAs I mentioned at the beginning, the HTML is an evolving language, currently `HTML5` is widely used, it has support for more semantic HTML elements than its prior versions. The browser must know the document format and version that we are using, this is done by using the `<!DOCTYPE>` declaration.\r\nDid you notice that I wrote \"declaration\" and not \"tag\"? This is because the declaration is not an HTML tag. It is \"information\" to the browser about what document type to expect.\r\n\r\nOkay, now that we know what to do, let us create this document for real this time.\r\n\r\nTo create an HTML document, any text editing software can be used. BUT, we are going to write plain text here, so it is better if you don't use a word processor. Use a simple text editor, like ed, vi, vim, Emacs, Atom, Notepad++, or in worst-case use Sublime Text, or VS Code. But if you have opened Microsoft Word, close it, we don't need it here.\r\n\r\nCreate a new folder in your system — name it whatever you feel like, it is not relevant — now create an empty file within that folder named `index.html`, this time, the name is important, make sure you spelled it correctly.\r\n\r\nThe name `index.html` is usually looked up by most tools that can be used as a server (homework, find the historical reasoning behind it). Though we are not using any such tools at this point, we will need it in the future, so let us stick to that naming convention for now.\r\n\r\nAfter creating the empty file, open it using your text editor and write the DOCTYPE to it. For HTML5, the DOCTYPE is written as `<!DOCTYPE html>` (yes, it is not a typo, it is `html` and not `html5`).\r\n\r\nThe HTML document has a few tags that need to be placed to be rendered properly. The first one is `<html>`, this is the root element of the HTML document, and there should be only one root element present in an HTML document.\r\n\r\nYour file should now look like this:\r\n\r\n```\r\n<!DOCTYPE html>\r\n<html>\r\n</html>\r\n```\r\n\r\nInside the `<html>` element, we can place two elements, one is `<head>` which can contain some additional information about the file, like the title of the document, information about the author, a brief description, how the document should be rendered, etc. For now, let us just add a title.\r\n\r\n```\r\n<!DOCTYPE html>\r\n<html>\r\n  <head>\r\n    <title>A simple HTML document</title>\r\n  </head>\r\n</html>\r\n```\r\n\r\nSimple? I will add two more things.\r\n\r\nTo support more languages, we can tell the browser to use the `UTF-8` character set when rendering the text by placing this `meta` tag within the `head` element:\r\n`<meta charset=\"UTF-8\">`.\r\n\r\nTo tell what language the webpage is using to the browser, and any assistive technology the visitor uses, we can use the `lang` attribute in the `html` tag:\r\n`<html lang=\"en\">`.\r\n\r\nJust like my website, I am using the International English here, for US english, you will need to use `en-us` instead. To find the correct code for the language you are using, try the [Language Subtag Lookup](https://r12a.github.io/app-subtags/). **You must do this because an empty lang tag means that the language is `undefined`**.\r\n\r\nThe other element we can place inside the `<html>` is the `<body>` element, all contents that we write should be placed within the `body`.\r\n\r\n```\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n  <head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>A simple HTML document</title>\r\n  </head>\r\n  <body>\r\n  </body>\r\n</html>\r\n```\r\n\r\nWhile we can place all the contents within the body, it is a good practice to further divide the sections. For someone using assistive technology, it would be nice to have indication of different **sections** of the page, we call them **landmarks**. We are not going to use them now, just keep it in your mind, by the end of this series you will learn when and how to use them.\r\n\r\nWhat you have now created is called an \"HTML boilerplate\", you can copy that file content and when creating more HTML files. Some IDEs (Integrated Development Environments) can generate this (and a bit more) for you, it is up-to you to figure that out.\r\n\r\nNow that the boilerplate is ready, let us put some content in it after remembering the rules:\r\n\r\n- A document must have only one `h1` tag.\r\n- Subheadings must not skip levels.\r\n- All paragraphs must use `p` tag.\r\n- Ensure that the document follows a semantic structure.\r\n\r\n```\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n  <head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>A simple HTML document</title>\r\n  </head>\r\n  <body>\r\n    <h1>Welcome to my simple HTML page</h1>\r\n    <p>\r\n      An HTML page is used for sharing information with others. And to ensure that <em>everyone</em> can access the page, we must be conscious of the <strong>semantic</strong> and <strong>accessibility</strong> when writing it.\r\n    </p>\r\n  </body>\r\n</html>\r\n```\r\n\r\nNow save the file with the above content, once saved, you can open the `index.html` file using your web browser. There are many ways to do this, one way is to just drag the `index.html` file into the web browser. Or you might be able to right-click on the file and open it using the default browser (this flow varies between OS and Desktop environments).\r\n\r\nOnce you have opened the file in the browser, you might see something like this screenshot.\r\n\r\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/1bbd4245-ef8b-4425-be5c-b7b68f602a06.png\" alt=\"Firefox screenshot\" width=\"1052\" height=\"437\"><figcaption>Firefox tab withA simple HTML documentas title. The webpage contains black text in white background. The heading saysWelcome to my simple HTML page, and it is rendered as a big and heavy text.The paragraph below the heading has the texteveryonerendered in italic style. The textssemanticandaccessibilityare rendered in bold letteres.</figcaption></figure>\r\n\r\n## Styling the web page\r\n\r\nThat screenshot above was very plain looking, and if you are reading my website in one of its dark themes, you *might* have strained your eyes, so how about we make it a bit better?\r\n\r\nUnlike the structure of the website, styling is not a compulsory thing to do. But just like the structure, one should be mindful of the semantic and accessibility of the page when styling an HTML page.\r\n\r\nOne of the main things that many still get wrong is the colour contrast, the default colour scheme of your website must have proper contrast. Remember, if you create a public website, or share links to it, you are effectively asking others to read it, it should not be a burden for them.\r\n\r\nFixing colour contrast used to be a hard thing to do, but now there are many tools that can help one to achieve good colour contrast. One of them is [WebAIM: Contrast Checker](https://webaim.org/resources/contrastchecker/).\r\n\r\nNow that we have decided to be inclusive of people with needs, let us learn how to style a web page!\r\n\r\nThere are multiple ways to style elements in HTML:\r\n\r\n- Using inline styles by using the `style` *attribute*\r\n- Using the `<style>` *tag* inside `<head>` *tag*.\r\n- Using a stylesheet, which is just the contents of the `<style>` placed in a separate file.\r\n\r\nWe will be using a stylesheet, which is easier to read, write, and manage. The other ways I mentioned to style HTML documents are usually discouraged as it makes managing styles hard, but feel free to explore how to do them on your own.\r\n\r\n### Stylesheets\r\n\r\nA stylesheet is a simple text file that contain styles in CSS (Cascading Style Sheet) format. The purpose of CSS is to change the look and feel of the web page, but as you learn more about it, you might get tempted to ignore the HTML structure completly, we will try to avoid that here.\r\n\r\nCSS is not just for styling HTML, it can be used to style SVG or XML documents, and some graphical user interface toolkits also supports styling via CSS, so it is always good to learn a bit about it.\r\n\r\nFor styles to work, we need to to tell the browser where to look for it. This is done by adding a `link` tag within the `head`. The syntax will look like `<link rel=\"stylesheet\" href=\"/path/to/style.css\">`. As you might have noticed, `link` is a void tag, you do not need to close it.\r\n\r\n#### The syntax\r\n\r\nThe CSS has a very simple syntax, a **selector**, **declaration block**, **properties**, and **values**. There are also some more advanced things like **media queries**, but the basics remains the same.\r\n\r\nIf you decide to have the main heading on your page to be shown in the center with red text, the following code shows a very simple CSS rule that would achieve that styling:\r\n\r\n```\r\nh1 {\r\n  color: red;\r\n  text-align: center;\r\n}\r\n```\r\n\r\n- In the above example, the CSS rule opens with a selector. This selects the HTML element that we are going to style. In this case, we are styling level one headings (`h1`).\r\n- We then have a set of curly braces `{ }`. This is the declaration block.\r\n- Inside the braces will be one or more **declarations**, which take the form of **property** and **value** pairs. We specify the property (`color` in the above example) before the colon, and we specify the value of the property after the colon (`red` in this example).\r\n- This example contains two declarations, one for `color` and the other for `text-align`. Each pair specifies a property of the element(s) we are selecting (`h1` in this case), then a value that we'd like to give to the property.\r\n\r\nLet us create a simple stylesheet first named **style.css** with the above example. We will create it in the same folder you placed the **index.html** file to.\r\n\r\nNow that we have a simple css file, add it inside the `head` tag witin the **index.html** file.\r\n\r\n```\r\n<link rel=\"stylesheet\" href=\"./style.css\">\r\n```\r\n\r\nYou will notice that the `href` is pointing to `./style.css`. The `\".\"` here is to tell that the path is **relative to the index.html** file, and you will probably never see it on a real website because there are better ways to do this when hosting it. I will talk about that later in this blog, let us focus on the style for now.\r\n\r\nYour **index.html** file should now look like this.\r\n\r\n```\r\n<html lang=\"en\">\r\n  <head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>A simple HTML document</title>\r\n    <link rel=\"stylesheet\" href=\"./style.css\">\r\n  </head>\r\n  <body>\r\n    <h1>Welcome to my simple HTML page</h1>\r\n    <p>\r\n      An HTML page is used for sharing information with others. And to ensure that <em>everyone</em> can access the page, we must be conscious of the <strong>semantic</strong> and <strong>accessibility</strong> when writing it.\r\n    </p>\r\n  </body>\r\n</html>\r\n```\r\n\r\nOnce you save those files and refresh your browser, the content remains the same, but you will see new the style like below.\r\n\r\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/ab2bd8b3-2e24-40b8-9c3d-762fa24cd4ce.png\" alt=\"Firefox screenshot\" width=\"1122\" height=\"347\"><figcaption>The headingWelcome to my simple HTML pageis now rendered as a big and heavy text in red. It is aligned to the center of the page.</figcaption></figure>\r\n\r\n#### Understanding selectors\r\n\r\nTo identify which element to style, we use `CSS selectors`. This can be its `tag name`, `id`, `class`, or any other `attributes`.\r\n\r\nIt is also possible to style an element based on `user action` like when a user `hover` over an element, or is curruntly `focusing` on it with a mouse or keyboard.\r\n\r\nThe possiblities are endless, so to make a CSS stylesheet easy to read, and to reduce the burden on the browser which calculates how to style things, we should use simple selectors while also avoiding style conflicts.\r\n\r\nI am going to provide some examples of selectors here, it might feel a bit overwhelming, but just like HTML elements, you do not need to understand them all. You will gradually learn as you work with them.\r\n\r\nWe have already seen how to style using an element's tag name, we just write it's tag name, i.e., to style `<h1>` element, we just write `h1` as its selector.\r\n\r\n```\r\n/*Styling all h1 elements */\r\nh1 {\r\n  /* some styles */\r\n}\r\n```\r\n\r\nThe text written between `/*`  and  `*/` are not considered styles, they are the way to do comments in CSS, and the browser will ignore it.\r\n\r\nTo style using an element's attribute, we use an attribute selector, a simple attribute selector can be written as `[attribute-name=\"value\"]`.\r\n\r\n```\r\n/* Styling all elements with the title attribute set as \"cheese\" */\r\n[title=\"cheese\"] {\r\n  /* some styles */\r\n}\r\n```\r\n\r\nTwo HTML attributes, `id`, and `class` are special, because they are used a lot for styling HTML.\r\nTo style using a `class`, we prefix the class name with a \".\", so the syntax will be `.class-name`.\r\nTo style using an element's `id`, we prefix the class name with a \"#\", so the syntax will be `#element-id`.\r\n\r\n```\r\n/* Styling all elements with the class \"cat\" */\r\n.cat {\r\n  /* some styles */\r\n}\r\n\r\n/* Styling element with the id \"mouse\" */\r\n#mouse {\r\n  /* some styles */\r\n}\r\n```\r\n\r\nIt is also possible to join multiple selectors together, to do that, we just write them together ***without any space between them***.\r\n\r\n```\r\n/* Styling all image elements with the class \"cat\" */\r\nimage.cat {\r\n  /* some styles */\r\n}\r\n```\r\n\r\nIf you put a space between selectors, that means that the selector preceeding is a child element. for example, the selector `#house .mouse .cheese` will style the element with the class `cheese` which is under an element with the class `mouse` which is under another element with the id `house`\r\n\r\n```\r\n/* Styling all elements with the class \"cat\" inside the element with id \"house\" */\r\n#house .cat {\r\n  /* some styles */\r\n}\r\n```\r\n\r\nLet us update the **style.css** file a bit, I'm setting a purple colour for the background, white text colour, removed the attention seeking red heading, and increased the text size for the paragraph.\r\n\r\n```\r\nbody {\r\n  background-color: purple;\r\n  color: white;\r\n  line-height: 2em;\r\n}\r\n\r\nh1 {\r\n  text-align: center;\r\n}\r\n\r\np {\r\n  font-size: 1.2em;\r\n}\r\n```\r\n\r\nLet us refresh the page again to see new the style.\r\n\r\n<figure><img src=\"/static/uploads/9bca2ae5-7643-4a1c-bb0c-fe37136f6cb1/e4004682-1fca-4c18-8119-33bf613f5653.png\" alt=\"Firefox screenshot with new changes\" width=\"1122\" height=\"347\"><figcaption>The background colour has now changed to purple, all the text, including the title is now in white color. There now a bit more gap between the lines in the paragraph, and its font size has increased sightly.</figcaption></figure>\r\n\r\nNow we have a problem, the colours we set might not be readable under some light conditions, and some prefers light theme while others need a dark theme. Some set their system or the browser to automatically change the theme based on time or location. It is one of the very convenient ways to avoid eye strain.\r\n\r\nWell, it is not just the visitors, you might also prefer one theme over the other most of the time. While many debate about what is good for the eyes even now, switching between dark and light themes is an accessibility feature, and we are considerate to people with needs, right?\r\n\r\nMost modern browsers now support a few convenient ways to tackle this in pure CSS.\r\n\r\n**Media queries**\r\n:   Media queries allow you to apply CSS styles depending on a device's media type (such as print vs. screen) or other features or characteristics such as screen resolution or orientation, aspect ratio, browser viewport width or height, or user preferences. These may include preferences such as preferring reduced motion, data usage, or transparency.\r\n\r\n**CSS custom properties (CSS variables)**\r\n:   CSS variables allow you to assign property values and dynamically change it. This is a powerful feature when combined with media queries or JavaScript (A programming language used to make websites more interactive).\r\n\r\n**color-scheme property**\r\n:   The color-scheme CSS property allows an element to indicate which colour schemes it can comfortably be rendered in. This is intended to help the browser style the form controls like text boxes and buttons, and scrollbars.\r\n\r\nI will write more about Media queries and dynamic styling in the next part of this series.","summary":"This post is for beginners who knows how to operate a computer, and wants an introductory course on web development.","date_published":"2024-03-19T11:20:51Z","date_modified":"2025-10-21T21:04:11Z","authors":[{"name":"Coding Otaku","url":"https://codingotaku.com/users/otaku"}]},{"id":"https://codingotaku.com/blogs/writing-a-website-part-1:-recipe-for-making-a-website!","url":"https://codingotaku.com/blogs/writing-a-website-part-1:-recipe-for-making-a-website!","title":"Writing a Website part 1: Recipe for Making a Website!","content_html":"<h2 id=\"why-a-recipe\">Why a Recipe?</h2>\n<p>Creating a project is like cooking. You need the right ingredients, follow a process, and have a bit of patients. Just like it is unhealthy to consume instant food, it is not good for one to rely on a service to publish their content.</p>\n<p>There are an interesting number of people even amoung techies who thinks that owning a website is hard and costs a lot. But it is not true, Domain names are cheep, and hosting a website can be done on a low end hardware with 1GB ram and enough GB to run an OS.</p>\n<p>Yes, you can host a website on a raspberryPI or other single board computers, but that’s not what I’m about to tell you here. I will give you the recipe, and just like any other recipe, how you cook is up to you!</p>\n<h2 id=\"ingredients\">Ingredients</h2>\n<ol>\n<li>Domain (optional)</li>\n<li>Hosting provider (optional)</li>\n<li>Content for the website.</li>\n</ol>\n<h2 id=\"instructions\">Instructions</h2>\n<h3 id=\"step-1-get-a-domain\">Step 1: Get a domain</h3>\n<p>:   #### Option 1: Using someone elses domain</p>\n<pre><code>Some code hosting services like [codeberg](https://docs.codeberg.org/codeberg-pages/), [gitlab](https://docs.gitlab.com/ee/user/project/pages/), and [github](https://pages.github.com/) let you create a **subdomain** to their site with your username and host your website at the same place, you can choose this option **if you do not want to pay for a domain**. The catch is that you will [need to create a static website](#step-2-option-1).\n\nThere are also other options like [ichi.city](https://ichi.city/) and [neocities](https://neocities.org/) where you can use a subdomain to create sites.\n\n#### Option 2: Owning a domain\n\nI suggest buying a domain, it is cheep and helps you create an identity. The challenging part is choosing a domain name that is not already taken by someone else, and some will [squat on a domain](https://en.wikipedia.org/wiki/Cybersquatting) and ask you to pay more for it, avoid them no matter how much you like that domain.\n\nI prefer [njalla](https://njal.la/) or [netcup](https://www.netcup.eu/) when buying a domain. Search for a domain regisrtar and find one that suites you (make sure to read the terms, some cheep registrars have very concerning terms).\n</code></pre>\n<h3 id=\"step-2-decide-how-it-behaves\">Step 2: Decide how it behaves</h3>\n<p>:   Before you go search for <em>free website builders without code</em>, you need to know a few things.</p>\n<pre><code>How a website behaves for a user decides the best way **you** can write it.\nIgnoring most of the techinal stuff, for a user there are two types of websites, **static** or **dynamic**.\n\n#### Option 1: Static website\n\nA **static webpage** is a simple text file usually formatted in HTML format to include images, videos, and styles.\nA **static website** is a collection of static webpages stored in sub folders.\n\nStatic websites are usually faster as it is not doing much computation, and it will be easier for you too as you don't need to worry about writing code to handle different user inputs or changing what you show to the user based on it. There are ways to do the latter using JavaScript, but it is not needed for a majority of websites like blogs.\n\nIt is perfectly possible to create a website without JavaScript or a code running in some server. [My previous website](https://codeberg.org/codingotaku/codingotaku.github.io-archive) did this, [the current one](/projects/website) is hosted on a virtual private server and runs in [rust](https://www.rust-lang.org/) (still no JavaScript).\n\n#### Option 2: Dynamic website\n\nA dynamic website, as the name suggests, is a generated at runtime. For example: when a user requests for a page, you can decide how to show it and what content it should have based on some logic.\n\nFor a dynamic website, you most likely need to rely on JavaScript. The basic things that you want to do can probably be written in vanilla JavaScript, but there are some JavaScript frameworks out there that claims to make things easier, search and find one that you feel comfortable with.\n\nIt is usually not necessary to create a dynamic website. But if you want the page to contain a user-driven flow like dashboards, fancy action-driven user interface (like [desktop.exeami.com](https://desktop.exeami.com)), etc, you can choose to build a dynamic website.\n</code></pre>\n<h3 id=\"step-3-host-the-website\">Step 3: Host the website</h3>\n<p>:   #### Option 1: Let someone else host it for you</p>\n<pre><code>For both static and dynamic websites, if you are not doing anything fancy like handlig user input for search, comments, forums, etc,. You can use some code hosting services like I mentioned in [using someone elses domain](#step-1-option-1) section. The links I provided there shows you how to build and host a webiste in their platform.\n\n#### Option 2: Use a hosting provider\n\nThere are many reasons for one to choose a hosting provider, usually it is to store things like comments, user generated images and videos, etc. And the ISP does not give you enough bandwidth for your traffic. In these cases, you might be able to use a hosting provider, and usually, this is also cheep (starting around $5 per month).\n\nI have used [njalla](https://njal.la/) and [vultr](https://www.vultr.com/) before, they are great. I currently use [netcup](https://www.netcup.eu/) to host things.\n\n#### Option 3: Self-host\n\nIf your website is small and has less traffic (this is you if you just started a blog), it might be possible to host the site at your own house if your Internet Service Provider (ISP) let you have a static IP address and enough bandwidth.\n</code></pre>\n<h3 id=\"step-4-finishing-touch\">Step 4: Finishing touch</h3>\n<p>:   #### Accessibility</p>\n<pre><code>Now that you have a website, you need to make it usable by others, a fancy animation that you liked and added could be trippy for someone, some colors you used might be unreadable due to low contrast, I recommend using [WAVE tool](https://wave.webaim.org/extension/) or [IBM Able extension](https://www.ibm.com/able/toolkit/tools#develop) to fix them, don't worry, they guide you on how and why you should do the changes they recommend.\n</code></pre>\n<p>Read the <a href=\"/blogs/writing-a-website-part-2\">part 2 of this series</a> to learn about writing a website using simple HTML and CSS.</p>\n","content_text":"## Why a Recipe?\r\n\r\nCreating a project is like cooking. You need the right ingredients, follow a process, and have a bit of patients. Just like it is unhealthy to consume instant food, it is not good for one to rely on a service to publish their content.\r\n\r\nThere are an interesting number of people even amoung techies who thinks that owning a website is hard and costs a lot. But it is not true, Domain names are cheep, and hosting a website can be done on a low end hardware with 1GB ram and enough GB to run an OS.\r\n\r\nYes, you can host a website on a raspberryPI or other single board computers, but that's not what I'm about to tell you here. I will give you the recipe, and just like any other recipe, how you cook is up to you!\r\n\r\n## Ingredients\r\n\r\n1. Domain (optional)\r\n2. Hosting provider (optional)\r\n3. Content for the website.\r\n\r\n## Instructions\r\n\r\n### Step 1: Get a domain\r\n:   #### Option 1: Using someone elses domain\r\n\r\n    Some code hosting services like [codeberg](https://docs.codeberg.org/codeberg-pages/), [gitlab](https://docs.gitlab.com/ee/user/project/pages/), and [github](https://pages.github.com/) let you create a **subdomain** to their site with your username and host your website at the same place, you can choose this option **if you do not want to pay for a domain**. The catch is that you will [need to create a static website](#step-2-option-1).\r\n\r\n    There are also other options like [ichi.city](https://ichi.city/) and [neocities](https://neocities.org/) where you can use a subdomain to create sites.\r\n\r\n    #### Option 2: Owning a domain\r\n\r\n    I suggest buying a domain, it is cheep and helps you create an identity. The challenging part is choosing a domain name that is not already taken by someone else, and some will [squat on a domain](https://en.wikipedia.org/wiki/Cybersquatting) and ask you to pay more for it, avoid them no matter how much you like that domain.\r\n\r\n    I prefer [njalla](https://njal.la/) or [netcup](https://www.netcup.eu/) when buying a domain. Search for a domain regisrtar and find one that suites you (make sure to read the terms, some cheep registrars have very concerning terms).\r\n\r\n### Step 2: Decide how it behaves\r\n:   Before you go search for *free website builders without code*, you need to know a few things.\r\n\r\n    How a website behaves for a user decides the best way **you** can write it.\r\n    Ignoring most of the techinal stuff, for a user there are two types of websites, **static** or **dynamic**.\r\n\r\n    #### Option 1: Static website\r\n\r\n    A **static webpage** is a simple text file usually formatted in HTML format to include images, videos, and styles.\r\n    A **static website** is a collection of static webpages stored in sub folders.\r\n\r\n    Static websites are usually faster as it is not doing much computation, and it will be easier for you too as you don't need to worry about writing code to handle different user inputs or changing what you show to the user based on it. There are ways to do the latter using JavaScript, but it is not needed for a majority of websites like blogs.\r\n\r\n    It is perfectly possible to create a website without JavaScript or a code running in some server. [My previous website](https://codeberg.org/codingotaku/codingotaku.github.io-archive) did this, [the current one](/projects/website) is hosted on a virtual private server and runs in [rust](https://www.rust-lang.org/) (still no JavaScript).\r\n\r\n    #### Option 2: Dynamic website\r\n\r\n    A dynamic website, as the name suggests, is a generated at runtime. For example: when a user requests for a page, you can decide how to show it and what content it should have based on some logic.\r\n\r\n    For a dynamic website, you most likely need to rely on JavaScript. The basic things that you want to do can probably be written in vanilla JavaScript, but there are some JavaScript frameworks out there that claims to make things easier, search and find one that you feel comfortable with.\r\n\r\n    It is usually not necessary to create a dynamic website. But if you want the page to contain a user-driven flow like dashboards, fancy action-driven user interface (like [desktop.exeami.com](https://desktop.exeami.com)), etc, you can choose to build a dynamic website.\r\n\r\n### Step 3: Host the website\r\n:   #### Option 1: Let someone else host it for you\r\n\r\n    For both static and dynamic websites, if you are not doing anything fancy like handlig user input for search, comments, forums, etc,. You can use some code hosting services like I mentioned in [using someone elses domain](#step-1-option-1) section. The links I provided there shows you how to build and host a webiste in their platform.\r\n\r\n    #### Option 2: Use a hosting provider\r\n\r\n    There are many reasons for one to choose a hosting provider, usually it is to store things like comments, user generated images and videos, etc. And the ISP does not give you enough bandwidth for your traffic. In these cases, you might be able to use a hosting provider, and usually, this is also cheep (starting around $5 per month).\r\n\r\n    I have used [njalla](https://njal.la/) and [vultr](https://www.vultr.com/) before, they are great. I currently use [netcup](https://www.netcup.eu/) to host things.\r\n\r\n    #### Option 3: Self-host\r\n\r\n    If your website is small and has less traffic (this is you if you just started a blog), it might be possible to host the site at your own house if your Internet Service Provider (ISP) let you have a static IP address and enough bandwidth.\r\n\r\n### Step 4: Finishing touch\r\n:   #### Accessibility\r\n\r\n    Now that you have a website, you need to make it usable by others, a fancy animation that you liked and added could be trippy for someone, some colors you used might be unreadable due to low contrast, I recommend using [WAVE tool](https://wave.webaim.org/extension/) or [IBM Able extension](https://www.ibm.com/able/toolkit/tools#develop) to fix them, don't worry, they guide you on how and why you should do the changes they recommend.\r\n\r\nRead the [part 2 of this series](/blogs/writing-a-website-part-2) to learn about writing a website using simple HTML and CSS.","summary":"Creating and hosting a website is not hard, and it is much cheeper than you think! And there are other options even if you don't want to pay.","date_published":"2023-09-16T00:00:00Z","date_modified":"2025-10-21T21:03:41Z","authors":[{"name":"Coding Otaku","url":"https://codingotaku.com/users/otaku"}]}]}