
Building Pretty Logseq: A Visual Enhancement Plugin for Logseq
Introduction
I've been using Logseq as my primary note-taking and knowledge management tool for the past year or so. It's essentially an outliner-style PKM (personal knowledge management) app where everything lives as interconnected pages in a local folder of Markdown files. I use it for meeting notes, project planning, research, and basically anything I want to remember later. The bidirectional linking and daily journals are what hooked me, but the more I used it, the more I found myself wanting to tweak little things about the interface.
The default page preview popovers felt cluttered. The sidebar took up too much space. Properties on pages looked like an afterthought. None of it was broken, just not quite how I wanted it. So I built a plugin to fix that.
What Pretty Logseq does
Pretty Logseq is a visual enhancement plugin that makes the Logseq interface cleaner and more information-dense. The core features break down into a few categories:
Custom popovers replace Logseq's default page preview with something that actually surfaces useful information. When you hover over a page link, you get a popover that automatically adapts to the page's properties. If it's a person with a photo property, you see the photo. If it's a resource with a URL, you see a formatted link. Ratings show as stars. The popover figures out what's useful and shows it without any configuration.
Sidebar and navigation tweaks let you reclaim screen space. I switched the sidebar to icon-only navigation, moved the graph selector to the bottom, and hid a few buttons I never use. It's still all there, just more compact.
Content styling improvements cover the stuff you look at all day: page properties get a subtle accent border and cleaner typography, tables from queries look polished instead of raw, and template blocks dim until you hover over them. These are small changes, but they add up.
How it's built
Logseq plugins are essentially web apps that run in an iframe and communicate with the host application through a JavaScript API. The plugin gets bundled as a single HTML file that loads your JavaScript, and Logseq handles the rest. I went with TypeScript, Vite for bundling, and SCSS for styles.
The architecture is built around a feature registry pattern. Each visual enhancement is a self-contained feature module that implements a simple interface:
interface Feature {
id: string;
name: string;
description: string;
getStyles(): string;
init(): void | Promise<void>;
destroy(): void;
}Features register themselves at startup, and the registry handles initialization, style aggregation, and cleanup. When you toggle a feature off in settings, the registry calls its destroy() method and refreshes the styles. When you toggle it back on, it calls init() again. This makes it straightforward to add new features without touching the core bootstrap code.
Styles are interesting because Logseq plugins inject CSS via a single logseq.provideStyle() call. Each feature provides its own SCSS file, and I use Vite's ?inline import suffix to get the compiled CSS as a string at build time:
import styles from './styles.scss?inline';
export const myFeature: Feature = {
id: 'myfeature',
getStyles() { return styles; },
// ...
};All the feature styles get aggregated together with base styles and injected in one shot. This keeps the plugin performant and makes it easy to conditionally include or exclude feature styles based on settings.
The popover system
The popover feature is where most of the complexity lives. The challenge is that Logseq has its own page preview system, and I needed to intercept hover events before Logseq handles them. The solution is event delegation with capturing phase listeners:
// Capturing phase intercepts before Logseq's handlers
doc.addEventListener('mouseenter', handleEnter, true);When you hover over a page reference, the manager starts a 300ms timer. If you're still hovering when it fires, it fetches the page data through the Logseq API, renders the popover content, and positions it near the anchor element. Moving your mouse to the popover keeps it open; moving away starts a hide timer. Click anywhere and it disappears.
The rendering itself uses a config-driven approach. Instead of having separate renderers for "Person" pages versus "Resource" pages versus everything else, I built a property inference system. It looks at what properties a page has and classifies them into roles: this property should be a subtitle, this one should be a detail row, this one should be a tag pill. The classification uses priority-ordered lists, so role beats position beats company for the subtitle slot.
const SUBTITLE_PRIORITY = ['role', 'position', 'company', 'type', 'category'];
const DETAIL_PRIORITY = ['location', 'email', 'phone', 'url', 'github', 'twitter'];Adding support for a new page type usually means adding a property name to one of these lists. The renderer handles the rest, including smart formatting like turning email addresses into mailto links and URLs into external links with favicons.
What I learned
Building a Logseq plugin taught me a few things about working with someone else's DOM. The main challenge is that you're a guest in a host application that can change its markup at any time. I spent more time than I'd like debugging issues that came down to Logseq updating something and breaking a selector I was relying on.
The iframe sandbox also has quirks. Plugins run isolated from the main Logseq window, which is good for security but means you need to use top.document instead of document to interact with the actual page. And you can't just import node modules that assume a normal browser environment.
The Logseq plugin API itself is pretty well designed. It gives you what you need for reading and writing content, responding to navigation events, and injecting styles. The documentation could be better, but the TypeScript types in @logseq/libs fill in most of the gaps.
Where to find it
The plugin is open source on GitHub if you want to use it or poke around the code. I'm planning to submit it to the Logseq marketplace once I've tested it a bit more across different themes and configurations. For now you can install it manually by cloning the repo, running yarn build, and loading it as an unpacked plugin in Logseq's developer mode.
If you're a Logseq user who's been wanting to customize the interface, hopefully this gives you some ideas. And if you're interested in building your own plugins, the codebase might be a useful reference for patterns like the feature registry and config-driven rendering.
Thanks for reading!