Obsidian Life Planner setup

Creating a Life OS Dashboard Setup in Obsidian for 2024(Part 4)

Setting up a homepage dashboard

Welcome back to the final edition of this series. This article will help you set up a homepage that looks like this:

1*uzFGkeKXQHE ftYDAq831Q

I published this screenshot on Reddit and was amazed by the responses. It was hard for many to believe that this was an Obsidian setup. 

But worry not, after this article, you will be able to create a much better setup than this. That’s my promise. 

So, let’s dive in. 

Talking about the elements on the homepage, here is the list in order:

  • Banner on the top
  • Buttons with different functions
  • Goal dashboard
  • 1-week habit tracker
  • Heatmap calendar view for tracking
  • Multi-column view with a link to important notes 
  • Multi-column view of recently updated and created notes


I don’t need to talk about the banner here again. Install the plugin. Add gradient style and add a property to your note called banner.


These are in-line buttons created with the meta-bind plugin. I told you previously we’ll be using the meta-bind plugin a lot in this setup. 

From the beginning till now we’ve been using it. Why? Because the plugin is so good and functional. 

It’s hard to teach you about the meta-bind plugin in this article. That can be a different article itself. Check out the documentation section to dive deep into the plugin. 

Here, I’ll share what I’ve done on this homepage. 

I created in-line buttons to perform certain functions. To do this, you need to create a reference block for the button.

Add this code to the very end of your note. 

style: primary
label: Open Daily Note
hidden: true
id: daily-note
type: command
command: daily-notes
style: primary
hidden: true
label: Open Weekly Note
id: weekly-note
type: command
command: periodic-notes:open-weekly-note
style: destructive
label: View Tasks
hidden: true
id: tasks
type: command
command: card-board:open-card-board-plugin-0

style: destructive
label: Light Mode
id: light-mode
hidden: true
- type: command
command: theme:use-light
style: primary
label: Dark Mode
id: dark-mode
hidden: true
- type: command
command: theme:use-dark

All of this code is hidden in the live preview mode. You can assign different functions to each button by changing the command in the code. 

To view the available commands, use the following dataviewjs query:


const getNestedObject = (nestedObj, pathArr) => {
return pathArr.reduce((obj, key) =>
(obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);

function hilite(keys, how) {
// need to check if existing key combo is overridden by undefining it
if (keys && keys[1][0] !== undefined) {
return how + keys.flat(2).join('+').replace('Mod', 'Ctrl') + how;
} else {
return how + '–' + how;

function getHotkey(arr, highlight=true) {
let hi = highlight ? '**' : '';
let defkeys = arr.hotkeys ? [[getNestedObject(arr.hotkeys, [0, 'modifiers'])],
[getNestedObject(arr.hotkeys, [0, 'key'])]] : undefined;
let ck = app.hotkeyManager.customKeys[arr.id];
var hotkeys = ck ? [[getNestedObject(ck, [0, 'modifiers'])], [getNestedObject(ck, [0, 'key'])]] : undefined;
return hotkeys ? hilite(hotkeys, hi) : hilite(defkeys, '');

let cmds = dv.array(Object.entries(app.commands.commands))
.sort(v => v[1].id, 'asc');

dv.paragraph(cmds.length + " commands currently enabled; " +
"non-default hotkeys <strong>bolded</strong>.<br><br>");

dv.table(["Command ID", "Name in current locale", "Hotkeys"],
cmds.map(v => [

All of this will be available in the demo vault. You may download the demo vault directly as well. 

Once you have the reference code for your buttons, add the following code block. 

`BUTTON[daily-note]` 🪴 `BUTTON[weekly-note]` 🪴 `BUTTON[light-mode, dark-mode]` 🪴 `BUTTON[tasks]`

//Here you use the label from the reference code for buttons.

Goal Tracking

1*EmUkk GmShFDpZyryoA0mA

For goal tracking, we use the following dataviewjs query:

> ```dataviewjs
> let goals = dv.pages('"Review/Goals"');
> dv.table(["Goal", "Deadline", "Progress"],
> goals.map(goal => [
> `<span style="font-size: 1.2em;">${goal.file.link}</span>`,
> goal.Deadline,
> `<progress value="${goal.progress}" max="${goal.target}"></progress><br>${Math.round((goal.progress / goal.target) * 100)}% completed`
> ])
> );
> ```

The callout format at the beginning is added to create multi-columns. We’ll talk about it next. 

Habit Tracking

The habit tracker displays the results of the last 7 days. Along with that, it displays the current streak and the streak record. 

This is all created with a dataviewjs query.

> const pages = dv.pages('"Review/Daily"')
> .where((p) => "Writing" in p || "Workout" in p || "Reading" in p)
> .sort((p) => p.file.day, "desc")
> .map((p) =>
> Object.create({
> link: p.file.link,
> day: p.file.day,
> Writing: p.Writing,
> Workout: p.Workout,
> Reading: p.Reading,
> })
> );
> function getStreak(validate) {
> let count = 0;
> for (const note of pages) {
> if (validate(note)) count++;
> else break;
> }
> return count;
> }
> function getRecord(validate) {
> let record = 0;
> let count = 0;
> for (const note of pages) {
> if (validate(note)) {
> count++;
> record = Math.max(record, count);
> } else {
> count = 0;
> }
> }
> return record;
> }
> const done = "✅";
> const skip = "🟥";
> const fileRows = pages
> .filter((p) => p.day >= moment().subtract(1, "w"))
> .sort((p) => p.day)
> .map((note) => [
> note.link,
> note.Writing ? done : skip,
> note.Workout ? done : skip,
> note.Reading ? done : skip,
> ]);
> const writing = [
> getStreak((note) => note.Writing),
> getRecord((note) => note.Writing),
> ];
> const workout = [
> getStreak((note) => note.Workout),
> getRecord((note) => note.Workout),
> ];
> const reading = [
> getStreak((note) => note.Reading),
> getRecord((note) => note.Reading),
> ];
> dv.table(
> ["Habits", "✍🏼", "💪🏼", "📖"],
> [
> ...fileRows,
> ["‎"],
> ["**Streak**", writing[0], workout[0], reading[0]],
> ["**Record**", writing[1], workout[1], reading[1]],
> ]
> );
> ```

Creating a multi-column view

To create a multi-column view, we use the Obsidian Modular CSS. This is not a plugin, but a CSS snippet that enables a multi-column view in Obsidian. 

To use these, there are two ways you can do it. 

First is manually downloading the CSS files from this GitHub repo and adding them to your vault’s snippet folder. 

Second, is a more recommended approach. Here’s how you do it. 

Now all of the CSS snippets will be inside the snippets folder which you can enable easily. 

If the instructions are hard to follow, here’s the YouTube tutorial

We added CSS files. Now what?

If you go to the documentation page of Obsidian modular CSS, there’s a lot to play with. We’ll keep it simple and just talk about what’s on my homepage here. 


To add this simple multi-column view, add a callout snippet with this code:

> [!multi-column]

Leave 1 space after that, and another space after the end of the first column. You can create more columns but I find 2 is a sweet spot for this purpose. 

Heatmap Calendar for Tracking

I dedicated an entire article on how to use the heatmap calendar plugin to visualize data in Obsidian. 

This calendar view visualizes my satisfaction/proudness level over a year. 


You saw there was a progress bar in our daily notes. Right? We use that data to visualize it over here. 

Here’s the snippet:

- ### Satisfaction #mcl/list-card
dv.span("**🏋️ Proudness 🏋️**")
const calendarData = {
colors: {
greeny: ["#B3FFD9", "#99FFC2", "#66FFB2", "#33FF99", "#00CC66"]
entries: []
for(let page of dv.pages('"Review/Daily"').where(p=>p.Proudness)){
date: page.file.name,
intensity: page.Proudness,
content: await dv.span(`[](${page.file.name})`), //for hover preview
renderHeatmapCalendar(this.container, calendarData)

This snippet makes use of the modular CSS cards layout to give that look. 

Then we have a simple quote below the heatmap calendar. 

A simple callout with one of my favorite quotes. 

> [!success]+ Remember
> **Even if we only did what we were capable of, we'd astound ourselves.**

Navigation links

This is again created with the modular CSS cards list view. 

Here’s the snippet:

Recently Created and updated files

This is created with a dataview query and modular CSS snippet beautifully wrapped up around the callout block. 

Here’s the snippet:

> [!multi-column]
> > [!summary] Recently Created
>> ```dataview
>> list
>> from ""
>> Sort file.ctime DESC
>> limit 7
>> [!Todo] Recently Updated
>> ```dataview
>> list
>> from ""
>> Sort file.mtime DESC
>> limit 7

And that’s all. You have created a homepage, you would love to come back to every single day. 

Obsidian Life OS Demo Vault

Here’s a link to Obsidian Life OS Demo Vault. Download it and follow along with the all tutorials I’ve provided.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.