# Sidebar Social Icons on Obsidian Publish > [!WARNING] README > All code present in this post and significant portions of the prose are output from a large language model, specifically Mistral Vibe (GLM). I feel I cannot reasonably take credit for model output that I could not have created myself, and wish to clearly state the model's authorship. I am sharing this to provide a feature/functionality back to other Obsidian Publish users. The previous version of this site was a static site built in Jekyll. (See: [[My (Old) New Site]]) For most of its existence it ran the [Hydejack](https://hydejack.com/) theme, which had a tidy little row of social media icons rendered under the site logo. It's a pleasant little way of saying "here's where else I exist." When I migrated to [Obsidian Publish](https://obsidian.md/publish), I went looking for such a setting and found... nothing. Not in the Publish settings, not in the docs, not even a "here's how I hacked it" blog post from someone more web dev savvy than me (and that's not a high bar to clear). Which brings us here. As best I could tell such a guide didn't exist, and through a little bit of prompting now it does, I think. At least I hope it's good enough for others to take, use, and iterate on. Fair warning: I am not a web developer. I'm a security guy, from a risk background, who can read JavaScript the way I can read Spanish; just enough that I can order a meal and spot when something's off. If I can get this working, you can too. > [!note] What you need > - An Obsidian Publish site (this is about [Publish](https://obsidian.md/publish), not on-your-workstation Obsidian). > - **A custom domain.** This is the big one: Publish only executes custom JavaScript on custom domains, not on the default `publish.obsidian.md` URL. ([Source](https://obsidian.md/help/publish/customize)) > - About twenty minutes and the ability to copy-paste. # `publish.js` and `publish.css` Publish quietly supports two special files that give you real control over your site: - **`publish.css`** — site-wide stylesheet, works on any Publish site. - **`publish.js`** — custom JavaScript, runs on every page but **requires a custom domain**. Both live in the **root of your vault**, both have to be created in an external editor (Obsidian can't create or edit `.css`/`.js` files, at least not natively), and both are published like any other note, but they are omitted from being indexed / listed on the live Publish site. Once the files exist, open the **Publish changes** dialog and they'll show up there, waiting to be published. Once `publish.js` is live on your site, it runs in every visitor's browser on every page. # The Approach This is a small script added to `publish.js` that waits for the page to load, finds the sidebar where Publish renders your site logo, and slots in a row of icon links right beneath it. Each icon is an [inline SVG](https://developer.mozilla.org/en-US/docs/Web/SVG), so a vector drawing embedded in the code itself, not fetched from a remote source. The script injects its own styling so you don't even need a `publish.css` to go with it. There are three things about this approach worth caring about, and they're why I'd recommend it even if Publish *had* a social icons setting: 1. **Zero external requests.** This draws each icon from coordinates in the script itself, no icon font, no third-party fetches, no extra DNS lookups. If you run a lean, privacy-conscious site (and if you're using Obsidian Publish, there's a decent chance you do), this keeps your page loads exactly as clean as they were before. 2. **It's self-contained.** One file. No build, no dependencies, nothing to keep updated except the file itself. 3. **It fails safe.** If Obsidian ever changes Publish's page structure, the icons just quietly stop appearing. The script is built defensively; it looks for the sidebar, and if it doesn't find it, it does nothing. ## Step 1: Get your icons You need the SVG path data for each icon you want. The easiest sources, roughly in order of license-friendliness: - **[Simple Icons](https://simple-icons.org/)** — brand logos, **CC0** (no attribution required). Bluesky, Mastodon, GitHub, RSS, and hundreds of others. - **[Bootstrap Icons](https://icons.getbootstrap.com/)** — general-purpose icons like the envelope, **MIT** license. - **[Font Awesome Free](https://fontawesome.com/search?ic=free)** — huge set; the *free* icons are **CC BY 4.0**, which means you need to give them credit somewhere (I put the attribution in a comment in my `publish.js` and included it in [[⚖️ Legal and Privacy#Usage Notices]]). Grab the `.svg` file for each icon and open it in any text editor — you'll see a short file containing one or more `<path d="..."/>` elements. That `d="..."` string is the actual drawing, and it's what you'll paste into the script below. > [!tip] Keep the `viewBox` > (Per GLM:) Each SVG has a `viewBox` attribute (like `0 0 24 24`) that defines its coordinate system. Copy that along with the path — the script uses it to scale the icon correctly. Mixing icons with different viewBoxes is fine. ## Step 2: Create your `publish.js` Open whatever text editor you like, and paste in the script below. Then customize the `SOCIAL_LINKS` array at the top — that's the only part most people will ever need to touch. Each entry is a label (for the tooltip and screen readers), a link, an optional hover color, and the SVG markup. Order in the array is order on the page. ```javascript /* * Hydejack-style social icon row for Obsidian Publish. * Injects icon links under the site logo in the left sidebar, on every page. * * SETUP * 1. Save this file at the ROOT of your publishing vault as "publish.js". * (Obsidian can't edit .js files — use an external editor.) * 2. In Obsidian, open "Publish changes" and publish the new "publish.js". * It won't appear in the file explorer; that's normal. * 3. Hard-refresh your live site. Icons appear under the logo. * * NOTE: custom JavaScript only runs on a custom domain, not on * publish.obsidian.md URLs. * * Icon credits (all embedded inline — zero external requests, no icon font): * - Bluesky: Simple Icons (CC0) * - Envelope: Bootstrap Icons (MIT) * - LinkedIn: Font Awesome Free 7.3.1 (Icons: CC BY 4.0, https://fontawesome.com/license/free) */ (function () { 'use strict'; // Edit this array to change the icons. Order = display order. var SOCIAL_LINKS = [ { label: 'Email me', href: 'mailto:[email protected]', brand: '', // empty = hover falls back to the site accent color svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true"><path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1zm13 2.383-4.708 2.825L15 11.105zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741M1 11.105l4.708-2.897L1 5.383z"/></svg>', }, { label: 'RSS feed', href: 'https://your-site.example.com/rss.xml', brand: '#FFA500', svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M19.199 24C19.199 13.467 10.533 4.8 0 4.8V0c13.165 0 24 10.835 24 24h-4.801zM3.291 17.415c1.814 0 3.293 1.479 3.293 3.295 0 1.813-1.485 3.29-3.301 3.29C1.47 24 0 22.526 0 20.71s1.475-3.294 3.291-3.295zM15.909 24h-4.665c0-6.169-5.075-11.245-11.244-11.245V8.09c8.727 0 15.909 7.184 15.909 15.91z"/></svg>', }, // Add your own entries here. For example: // { // label: 'LinkedIn', // href: 'https://www.linkedin.com/in/yourhandle/', // brand: '#0A66C2', // svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" aria-hidden="true"><path d="M100.3 448l-92.9 0 0-299.1 92.9 0 0 299.1zM53.8 108.1C24.1 108.1 0 83.5 0 53.8 0 39.5 5.7 25.9 15.8 15.8s23.8-15.8 38-15.8 27.9 5.7 38 15.8 15.8 23.8 15.8 38c0 29.7-24.1 54.3-53.8 54.3zM447.9 448l-92.7 0 0-145.6c0-34.7-.7-79.2-48.3-79.2-48.3 0-55.7 37.7-55.7 76.7l0 148.1-92.8 0 0-299.1 89.1 0 0 40.8 1.3 0c12.4-23.5 42.7-48.3 87.9-48.3 94 0 111.3 61.9 111.3 142.3l0 164.3-.1 0z"/></svg>', // }, ]; var CSS = '.site-body-left-column-site-name { text-align: center; }' + '#social-icons { display: flex; gap: 10px; align-items: center; justify-content: center;' + ' padding-left: 0 !important; margin: 4px 0 18px; }' + '.social-icon { display: inline-flex; align-items: center; justify-content: center;' + ' width: 24px; height: 24px; line-height: 0; color: var(--text-muted, #999);' + ' transition: color 0.15s ease, transform 0.15s ease; }' + '.social-icon svg { width: 20px; height: 20px; fill: currentColor; }' + '.social-icon:hover { color: var(--brand-color, var(--text-accent, #000)); transform: scale(1.15); }'; function buildStyleEl() { var style = document.createElement('style'); style.id = 'social-icons-style'; style.textContent = CSS; return style; } function buildIconRow() { var row = document.createElement('div'); row.id = 'social-icons'; SOCIAL_LINKS.forEach(function (item) { var a = document.createElement('a'); a.className = 'social-icon'; a.href = item.href; a.title = item.label; a.setAttribute('aria-label', item.label); if (item.brand) a.style.setProperty('--brand-color', item.brand); if (/^https?:/i.test(item.href)) { a.target = '_blank'; a.rel = 'noopener noreferrer'; } a.innerHTML = item.svg; row.appendChild(a); }); return row; } function inject() { if (document.getElementById('social-icons')) return; var column = document.querySelector('.site-body-left-column'); if (!column) return; // sidebar not present yet (or navigation disabled) if (!document.getElementById('social-icons-style')) { document.head.appendChild(buildStyleEl()); } // Anchor the row on its own line directly below the site title, // inside whatever container holds the title, so it starts at the // same left edge as the rest of the sidebar content. var anchor = column.querySelector('.site-body-left-column-site-name') || column.querySelector('.site-header') || column.querySelector('img'); // logo image fallback var row = buildIconRow(); if (anchor && anchor.parentNode) { anchor.parentNode.insertBefore(row, anchor.nextSibling); } else { column.insertBefore(row, column.firstChild); } // Line the icons up with the start of the navigation text: if the // row landed flush with the column edge (its container has no inset // of its own), copy the nav container's left padding onto the row. if (Math.abs(row.getBoundingClientRect().left - column.getBoundingClientRect().left) < 1) { var nav = column.querySelector('.nav-view-outer') || column.querySelector('.nav-view'); if (nav) { var navPad = getComputedStyle(nav).paddingLeft; if (navPad && navPad !== '0px') row.style.paddingLeft = navPad; } } } function start() { inject(); // Publish is a single-page app; re-inject if the DOM is ever rebuilt. var observer = new MutationObserver(function () { inject(); }); observer.observe(document.body, { childList: true, subtree: true }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { start(); } })(); ``` A few notes on what's happening in there, for the curious: - **`.site-body-left-column`** is the CSS class Publish uses for the left sidebar (the one showing your site navigation). Inside it, Publish renders your logo and site name; the script finds that block and inserts the icon row directly after it, inside whatever container holds the title. If the row lands flush with the sidebar edge, the script copies the nav menu's left padding onto it so the icons line up with the navigation text — unless you're using the centered alignment, where the stylesheet's `padding-left: 0 !important` cancels that out (more on that below). If Obsidian ever renames those classes, this is the part you'd need to update — pop open your browser's dev tools, inspect the sidebar, and adjust. - **The `MutationObserver`** watches for changes to the page's DOM. Publish behaves like a single-page app, so this makes sure the icons survive anything Publish does after initial load, including re-inserting them if they ever get wiped. - **Icons inherit Publish's theme colors.** At rest they use `--text-muted`, one of Publish's own variables, so they automatically match whatever theme you're on, light or dark. On hover they scale up slightly and switch to each brand's color — the row reads as understated until you interact with it. - **The email link has no hover color** — `brand` is left empty, so it falls back to your site's accent color. Brand it up if you want. - **RSS is a nice freebie:** if you're on Obsidian Publish, your feed already exists at `/rss.xml` whether you've advertised it or not — add an icon entry pointing at it and readers can subscribe in one click. - Note that Obsidian [Publish RSS feed](https://forum.obsidian.md/t/improve-the-rss-feed-for-obsidian-publish-reverse-chronological-order/79041) only provides the post title and link. Very limited, feels like an afterthought, but it's better than nothing I guess. ## Pick your alignment We shipped two layouts while building this, so you get both. The script above comes **centered**: the site title and the icon row both line up under the logo, which reads like a signature block. The other look is **left-aligned with the navigation text**: the icons sit below the title on their own row, flush with wherever your nav menu's text starts. The only difference between them is the first couple of lines in the `CSS` block, so switching is a paste-over. **Centered (the default in the script above):** ```javascript var CSS = '.site-body-left-column-site-name { text-align: center; }' + '#social-icons { display: flex; gap: 10px; align-items: center; justify-content: center;' + ' padding-left: 0 !important; margin: 4px 0 18px; }' + ``` **Left-aligned with the nav text (swap the above for this):** ```javascript var CSS = '#social-icons { display: flex; gap: 10px; align-items: center; margin: 4px 0 18px; }' + ``` Two details worth understanding so the swap doesn't surprise you: - The centered version adds `.site-body-left-column-site-name { text-align: center; }` to center the site title so it matches the centered logo and icons. If your theme already centers the title, the line is harmless. - The `padding-left: 0 !important` in the centered version isn't decoration. Remember the auto-alignment from the notes above: the script detects when the icon row lands flush with the sidebar edge and copies the nav menu's left padding onto the row. Great for the left-aligned look; in the centered layout that same padding would nudge the icons right of true center. An `!important` in the stylesheet beats the inline style, so the row stays dead-center. In the left-aligned version, you drop that line and let the auto-alignment do its job — that's the feature, not the bug. ## Step 3: Publish it Save the file as `publish.js` in the **root** of your vault. Back in Obsidian, open the **Publish changes** dialog; `publish.js` will appear in the list of new files. Publish it, then hard-refresh your live site (`Ctrl/Cmd+Shift+R`). And there should be the icons. Under your logo. On every page. # Troubleshooting - **Icons don't appear at all.** Are you on your custom domain? `publish.js` is inert on `publish.obsidian.md` URLs — that's a Publish platform rule, not a bug in the script. - **Still nothing on the custom domain.** Is your site's navigation sidebar enabled? The script attaches to it; if you've turned navigation off, there's no sidebar to decorate. Publish may also cache aggressively — hard refresh, and check the file actually went out in the Publish changes dialog. - **Icons appear, then vanish or flicker.** You've probably got two copies of the script or another customization touching the same DOM. The script is written to detect its own row and back off if it's already there, but two differently-named copies would double up. - **It worked for months and then stopped.** Obsidian updated Publish's page structure and the class names changed. The script will do nothing rather than something wrong — open dev tools, find the current sidebar class, update the selectors, republish. # Icon Licenses and Accessibility Two things worth doing right so this stays defensible: **Licenses.** Simple Icons is CC0, Bootstrap Icons is MIT — do what you like. Font Awesome's *free* icons are CC BY 4.0, which asks for attribution. We opted to include a license comment from their SVG inside `publish.js` so the credits travel with the code. If your site has a legal, license, or credits page, a one-liner there doesn't hurt. I included this in [[⚖️ Legal and Privacy#Usage Notices]] for the sake of being thorough. **Accessibility.** Each icon link carries a `title` and an `aria-label`, so screen readers announce "LinkedIn" instead of silence or a URL slug. The icons are also keyboard-navigable because they're real anchor elements. # Steal This Everything on this site is [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), this post and script included. Take it, futz with it, make it yours. If you publish an improved version, please tell me because I'll happily link through to other and better implementations.