Sorry Tana, Roam can do that too: Supertags

In this post, I’ll explain exactly what supertags are, how they work in Tana, and how you can easily replicate most of the functionality in Roam Research.

TLDR;

  • The features most associated with supertags are fields with forced structure and default content
  • We can recreate the functionality of supertags in Roam using the Smartblocks + Query Builder plugins
  • Tana’s implementation is ahead in some areas, most noticeably the highly polished views

Sneak peak of what we’re building

By the end of this post, you’ll know exactly how to build a supertag-like SmartBlock in Roam Research.

Our custom SmartBlock will have features like structured fields and instances – things you probably thought were only available in Tana or other database-centric tools like Notion (spoiler alert, they’re not). I’ll provide you with the complete SmartBlock workflow and the custom CSS, so your graph can look just like the videos in this tutorial.

What the heck are supertags anyway?

Jump to the section where you build this in Roam

Supertags in Tana cover a lot, but for this article, we’re going to focus on fields, forced structure, and default content.

Fields

Fields allow us to add additional structure and information to a supertag object. An example is helpful to illustrate:

Let’s say that we’re building a supertag to manage our projects. Each time we create a new project, we also want to set some additional fields to help keep our projects organized. Those additional fields might look something like this:

  • Project name
  • Client
  • Status
  • Assignee
  • Created date
  • Due date

Forced structure

Forced structure is just a way of saying: when we populate this field, it must be a particular type or format. Building on our project setup above that would look something like this:

  • Project name: Free text – Input field
  • Client: Dynamic options – Only items I’ve already marked client
  • Status: Fixed options – One of: Not Started, In Progress, Complete
  • Assignee: Graph users – Auto-initialize to user that’s triggering
  • Created date: Date format – Auto-initialize to today’s date
  • Due date: Date format – User selected

Default content

Default content is the ability to automatically populate additional content each time we create a new supertag instance. The content could be simple static text, or a complex dynamic query (live search). Regardless of complexity, the point here is that we only need to define the format and structure of the content once, but we get it (by default) each time.

Building on our example above, each time we create a new project, we also want to create a corresponding tasks table that automatically outputs all to-do’s assigned to this project.

I originally planned on building this supertag in Tana (yes, I have access), but that would make the post even longer, and it’s already too long. I’ll let you search the interwebs for others who have built supertags in Tana to get an idea of what that’s like.

Building a supertag in Roam Research

Using the project outline above as our guide, let’s see if we can create that exact supertag in Roam Research. This means we’ll need to accomplish the following:

  1. Set additional fields (client, status, due date, etc…) every time a new project is created
  2. Ensure the additional fields are of the exact type and format we specified in the outline
  3. Automatically create and output a tasks table capturing all todo’s referencing the project

Ready? Let’s jump in. To replicate supertags in Roam, we’re going to be using two plugins from the Roam Depot:

  • SmartBlocks – API for fetching and manipulating your Roam data + build custom commands and workflows
  • Query Builder – Interface for querying and viewing your Roam data

That’s it. With just those two plugins, we have everything we need to build our project SmartBlock. Here’s how we do it:

Step 1: Build a clients table with Query Builder

How do we enforce that our client field is only populated with clients? Simple; we create a query that retrieves all clients, and then we limit the input options to the results.

Our simple list of clients

A simple structure to denote what a client is in our graph. You might use a tag system #Client, attributes, or another method. If you have questions or need help regarding your specific setup, I recommend you jump into the Roam Slack.

Query params to fetch our clients

To make a new query, go to any block and type {{query block}}. Here we’re using two conditions: has parent, which takes a variable (clientParent), and references title, which auto-fills your graph’s links as you type.

Final “clients table” output

A simple table with all of our clients is the final output of our query block. Where you decide to put your query block does not matter. What does matter for our next step is recording the table’s block reference ((lLZQSXA5W)).

Step 2: Build a workflow to set our project fields

Here we’re building the workflow that will prompt us to enter values for the additional fields we want to set each time a new project is created. If you’re brand new to SmartBlocks, the RoamJS site has a nice primer on syntax and command structure.

#SmartBlock Project
    <%SET:name,<%INPUT:Project name%>%> <%NOBLOCKOUTPUT%>
    <%SET:client,<%INPUT:Client,<%QUERYBUILDER:sUWHCHyYp,{text}%>%>%> <%NOBLOCKOUTPUT%>
    <%SET:status,<%INPUT:Status%%Not Started%%In-Progress%%Complete%>%> <%NOBLOCKOUTPUT%>
    <%SET:assignee,<%CURRENTUSER%>%> <%NOBLOCKOUTPUT%>
    <%SET:created,<%DATE:today%>%> <%NOBLOCKOUTPUT%>
    <%SET:due,<%INPUT:Due in how many days?%>%> <%NOBLOCKOUTPUT%>

Don’t copy/paste this into Roam! It’s a block-based tool; the workflow above is not in blocks. Click here to get the working SmartBlock.

Line-by-line code explanation

  • #SmartBlock Project
    • Register a new SmartBlock called Project
  • <%SET:name,<%INPUT:Project name%>%> <%NOBLOCKOUTPUT%>
    • Set variable name (Project Name) from a free-text input
  • <%SET:client,<%INPUT:Client,<%QUERYBUILDER:sUWHCHyYp,{text}%>%>%>
    • Set variable client from a dropdown populated by the clients tables (sUWHCHyYp)
  • <%SET:status,<%INPUT:Status%%Not Started%%In-Progress%%Complete%>%>
    • Set variable status from a dropdown populated by fixed options
  • <%SET:assignee,<%CURRENTUSER%>%> <%NOBLOCKOUTPUT%>
    • Set variable assignee from native SmartBlock command
  • <%SET:created,<%DATE:today%>%> <%NOBLOCKOUTPUT%>
    • Set variable created from native SmartBlock date command
  • <%SET:due,<%INPUT:Due in how many days?%>%> <%NOBLOCKOUTPUT%>
    • Set variable due from a free text input
    • ** We’ll turn that number into a date in the next step

Step 3: Create a new project page with the values

Here we’re taking the field values set in the previous step and using them to populate a new project page. We’re utilizing Roam’s attributes to define the project, but this workflow can easily be modified to use tags or links.

#SmartBlock Generate Project <%HIDE%>
    <%SET:projectPage,<%CURRENTPAGENAME%>%> <%NOBLOCKOUTPUT%>
    For Client:: <%GET:client%>
    Owner:: <%HASHTAG:<%GET:assignee%>%>
    Status:: <%HASHTAG:<%GET:status%>%>
    Due Date:: <%DATE:in <%GET:days%> days%>
#SmartBlock Project
    <%SET:name,<%INPUT:Project name%>%> <%NOBLOCKOUTPUT%>
    <%SET:client,<%INPUT:Client,<%QUERYBUILDER:sUWHCHyYp,{text}%>%>%> <%NOBLOCKOUTPUT%>
    <%SET:status,<%INPUT:Status%%Not Started%%In-Progress%%Complete%>%> <%NOBLOCKOUTPUT%>
    <%SET:assignee,<%CURRENTUSER%>%> <%NOBLOCKOUTPUT%>
    <%SET:created,<%DATE:today%>%> <%NOBLOCKOUTPUT%>
    <%SET:due,<%INPUT:Due in how many days?%>%> <%NOBLOCKOUTPUT%>
    <%SMARTBLOCK:Generate Project,<%GET:name%>%> <%NOBLOCKOUTPUT%>
    <%IFVAR:projectPage,/.+/%> <%SIDEBARWINDOWOPEN:<%GET:projectPage%>%><%TAG:<%GET:projectPage%>%>

Don’t copy/paste this into Roam! It’s a block-based tool; the workflow above is not in blocks. Click here to get the working SmartBlock.

Line-by-line code explanation

Covering only the newly added code from step 1 (starting at line 14 and working down, then back up from line 1 to line 6)

  • <%SMARTBLOCK:Generate Project,<%GET:name%>%> <%NOBLOCKOUTPUT%>
    • Run the SmartBlock “Generate Project” and output on a page with a title that equals the name variable
    • * If there is no page with that title, SmartBlocks will create one for us
  • <%IFVAR:projectPage,/.+/%> <%SIDEBARWINDOWOPEN:<%GET:projectPage%>%><%TAG:<%GET:projectPage%>%>
    • If variable projectPage was set on Generate Project SmartBlock, open it in the right sidebar
  • #SmartBlock Generate Project <%HIDE%>
    • Register a new custom SmartBlock called Generate Project
  • <%SET:projectPage,<%CURRENTPAGENAME%>%> <%NOBLOCKOUTPUT%>
    • Set variable projectPage from native SmartBlock command <%CURRENTPAGENAME%>
  • For Client:: <%GET:client%>
    • Output “For Client” attribute with value client
  • Owner:: <%HASHTAG:<%GET:assignee%>%>
    • Output “Owner” attribute with value assignee
  • Status:: <%HASHTAG:<%GET:status%>%>
    • Output “Status” attribute with value status
  • Due Date:: <%DATE:in <%GET:days%> days%>
    • Output “Due Date” attribute with value days
    • * Days is wrapped in a built-in SmartBlock command <%DATE:%> that runs natural language processing

Bonus 1 – Improving the UX / UI of our dialog prompt

Customizing the look and feel of our graph is trivially easy in Roam, so let’s take advantage of that. The custom CSS below improves the stock design and adds carefully selected focus styles so you can quickly whip through the entire workflow without ever having to touch your mouse.

Standard dialog design

Custom dialog design

CSS for custom dialog prompt
.roamjs-query-builder-parent {
    margin-top: .5rem;
    box-shadow:
            0 1px 1px hsl(0deg 0% 50% / 0.075),
            0 2px 2px hsl(0deg 0% 50% / 0.075),
            0 4px 4px hsl(0deg 0% 50% / 0.075),
            0 8px 8px hsl(0deg 0% 50% / 0.075),
            0 16px 16px hsl(0deg 0% 50% / 0.075);
}
.roamjs-query-results-view > span.absolute > p {
    padding-left: 1rem;
    font-size: .9rem;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog {
    background: #222326;
    width: 750px;
    min-width: 750px;
    padding: 2.5rem;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog h3.bp3-heading {
    display: none;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-contents,
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-label
{
    width: 100%;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body > .bp3-label > .bp3-popover-wrapper > .bp3-popover-target > div {
    border-radius: 6px;
    display: inline-block;
    background-color: #F3F7FF;
}

#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body > .bp3-label > .bp3-popover-wrapper > .bp3-popover-target > div > button {
    background: transparent;
    min-width: 16rem;
    display: flex;
    justify-content: space-between;
    border-radius: 6px;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body > .bp3-label > .bp3-popover-wrapper > .bp3-popover-target > div > button > span {
    font-size: 1rem;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body > .bp3-label > .bp3-popover-wrapper > .bp3-popover-target > div > button .bp3-icon {
    color: #18191b;
}

#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button > span {
    font-weight: bold;
    -webkit-font-smoothing: antialiased;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer {
    margin-top: 1rem;
}
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content {
    min-width: 16rem !important;
}

/*  (Light) */
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-overlay-backdrop {
    background: rgba(0,0,0,.65);
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog {
    padding: 0 !important;
    background-color: transparent !important;
    border-radius: 0.5rem;
    border: 1px solid #d6d7db;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body {
    padding: 1.25rem;
    background-color: #fff !important;
    border-top-left-radius: 0.5rem;
    border-top-right-radius: 0.5rem;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer {
    padding: .85rem 1.5rem !important;
    background-color: rgb(249,250,251) !important;
    margin-top: 0 !important;
    border-bottom-left-radius: 0.5rem;
    border-bottom-right-radius: 0.5rem;
    border-top: 1px solid #eee;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-label {
    color: rgb(17,24,39) !important;
    white-space: pre-wrap;
    font-size: 1rem;
    font-weight: bolder;
    -webkit-font-smoothing: antialiased;
    margin-bottom: 0;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-input-group input[type="text"]{
    margin-top: 0;
    min-width: 16rem;
    font-size: 1rem;
    color: #18191b;
    font-weight: 500;
    height: 40px;
    line-height: 40px;
    background-color: #F3F7FF;
    -webkit-font-smoothing: initial;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-input-group input[type="text"]::placeholder {
    font-weight: normal;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-input-group input[type="text"]:focus,
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body > .bp3-label > .bp3-popover-wrapper > .bp3-popover-target > div:focus-within
{
    background-color: #fff;
    outline: 1px solid #0050F0 !important;
    outline-offset: 0;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body > .bp3-label > .bp3-popover-wrapper,
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-input-group {
    margin-top: .75rem;
}
/* Footer */
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button {
    padding: 8px 16px;
    font-weight: 500;
    line-height: 20px;
    border-radius: 0.375rem;
    outline-style: solid;
    outline-width: 1px;
    outline-offset: 0;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:focus,
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:hover
{
    outline-width: 1px !important;
    outline-style: solid !important;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:first-of-type {
    background-color: #85adff;
    outline-color: #528bff;
    color: #fafafa;
    margin-left: 12px;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:first-of-type:focus,
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:first-of-type:hover
{
    background: #3A7BFF;
    outline-color: #226BFF;
    color: #fff;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:last-of-type {
    background-color: #fff;
    outline-color: rgb(209,213,219) !important;
    color: #57667f;
}
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:last-of-type:focus,
#smartblocks-prompt + .bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:last-of-type:hover
{
    background-color: transparent;
    outline-color: #8d97a5 !important;
    color: #29313d;
}
/* Dropdowns */
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content,
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content > div > ul.bp3-menu {
    background-color: #fff;
}
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content > div > ul.bp3-menu li:not(:last-of-type) {
    border-bottom: 1px solid #eee;
}
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content > div > ul.bp3-menu li a,
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content > div > ul.bp3-menu li a div {
    color: rgb(15,23,42);
}
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content > div > ul.bp3-menu li a.bp3-active,
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content > div > ul.bp3-menu li a.bp3-active div {
    color: #030303;
}
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content > div > ul.bp3-menu li > .bp3-menu-item.bp3-intent-primary.bp3-active {
    background-color: rgb(241,245,249);
}
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content > div > ul.bp3-menu li:hover > a,
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content > div > ul.bp3-menu li > a:hover {
    background-color: #dbe6f0;
}
.roamjs-prompt-dropdown > .bp3-transition-container > .bp3-popover > .bp3-popover-content > div > ul.bp3-menu li a div {
    font-size: 1rem;
    padding-top: 4px;
    padding-bottom: 4px;
}
Minified CSS
.roamjs-query-builder-parent{margin-top:.5rem;box-shadow:0 1px 1px hsl(0deg 0% 50% / .075),0 2px 2px hsl(0deg 0% 50% / .075),0 4px 4px hsl(0deg 0% 50% / .075),0 8px 8px hsl(0deg 0% 50% / .075),0 16px 16px hsl(0deg 0% 50% / .075)}.roamjs-query-results-view>span.absolute>p{padding-left:1rem;font-size:.9rem}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog{background:#222326;width:750px;min-width:750px}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog h3.bp3-heading{display:none}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-contents,#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-label{width:100%}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body>.bp3-label>.bp3-popover-wrapper>.bp3-popover-target>div{border-radius:6px;display:inline-block;background-color:#f3f7ff}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body>.bp3-label>.bp3-popover-wrapper>.bp3-popover-target>div>button{background:0 0;min-width:16rem;display:flex;justify-content:space-between;border-radius:6px}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body>.bp3-label>.bp3-popover-wrapper>.bp3-popover-target>div>button>span{font-size:1rem}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body>.bp3-label>.bp3-popover-wrapper>.bp3-popover-target>div>button .bp3-icon{color:#18191b}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button>span{font-weight:700;-webkit-font-smoothing:antialiased}.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content{min-width:16rem!important}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-overlay-backdrop{background:rgba(0,0,0,.65)}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog{padding:0!important;background-color:transparent!important;border-radius:.5rem;border:1px solid #d6d7db}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body{padding:1.25rem;background-color:#fff!important;border-top-left-radius:.5rem;border-top-right-radius:.5rem}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer{padding:.85rem 1.5rem!important;background-color:#f9fafb!important;margin-top:0!important;border-bottom-left-radius:.5rem;border-bottom-right-radius:.5rem;border-top:1px solid #eee}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-label{color:#111827!important;white-space:pre-wrap;font-size:1rem;font-weight:bolder;-webkit-font-smoothing:antialiased;margin-bottom:0}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-input-group input[type=text]{margin-top:0;min-width:16rem;font-size:1rem;color:#18191b;font-weight:500;height:40px;line-height:40px;background-color:#f3f7ff;-webkit-font-smoothing:initial}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-input-group input[type=text]::placeholder{font-weight:400}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body>.bp3-label>.bp3-popover-wrapper>.bp3-popover-target>div:focus-within,#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-input-group input[type=text]:focus{background-color:#fff;outline:#0050F0 solid 1px!important;outline-offset:0}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-body>.bp3-label>.bp3-popover-wrapper,#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-input-group{margin-top:.75rem}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button{padding:8px 16px;font-weight:500;line-height:20px;border-radius:.375rem;outline-style:solid;outline-width:1px;outline-offset:0}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:focus,#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:hover{outline-width:1px!important;outline-style:solid!important}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:first-of-type{background-color:#85adff;outline-color:#528bff;color:#fafafa;margin-left:12px}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:first-of-type:focus,#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:first-of-type:hover{background:#3a7bff;outline-color:#226bff;color:#fff}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:last-of-type{background-color:#fff;outline-color:#d1d5db!important;color:#57667f}#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:last-of-type:focus,#smartblocks-prompt+.bp3-portal .bp3-overlay .bp3-dialog .bp3-alert-footer button:last-of-type:hover{background-color:transparent;outline-color:#8d97a5!important;color:#29313d}.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content,.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content>div>ul.bp3-menu{background-color:#fff}.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content>div>ul.bp3-menu li:not(:last-of-type){border-bottom:1px solid #eee}.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content>div>ul.bp3-menu li a,.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content>div>ul.bp3-menu li a div{color:#0f172a}.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content>div>ul.bp3-menu li a.bp3-active,.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content>div>ul.bp3-menu li a.bp3-active div{color:#030303}.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content>div>ul.bp3-menu li>.bp3-menu-item.bp3-intent-primary.bp3-active{background-color:#f1f5f9}.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content>div>ul.bp3-menu li:hover>a,.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content>div>ul.bp3-menu li>a:hover{background-color:#dbe6f0}.roamjs-prompt-dropdown>.bp3-transition-container>.bp3-popover>.bp3-popover-content>div>ul.bp3-menu li a div{font-size:1rem;padding-top:4px;padding-bottom:4px}

Bonus 2 – Setup conditionals to control the output of our workflow

This step is optional, but quite helpful. By default, SmartBlocks will run every command we define (which is good), but what if we triggered it by accident or need to back out halfway through for whatever reason?

With conditionals, we can ensure that we’ve captured a value from the previous prompt before the workflow moves on to the next.

Standard workflow

Workflow with conditionals

#SmartBlock Generate Project <%HIDE%>
    <%SET:projectPage,<%CURRENTPAGENAME%>%> <%NOBLOCKOUTPUT%>
    For Client:: <%GET:client%>
    Owner:: <%HASHTAG:<%GET:assignee%>%>
    Status:: <%HASHTAG:<%GET:status%>%>
    Due Date:: <%DATE:in <%GET:due%> days%>
#SmartBlock Project
    <%SET:name,<%INPUT:Project name%>%> <%NOBLOCKOUTPUT%>
    <%IFVAR:name,/.+/%><%SET:client,<%INPUT:Client,<%QUERYBUILDER:sUWHCHyYp,{text}%>%>%> <%NOBLOCKOUTPUT%>
    <%IFVAR:client,/.+/%><%SET:status,<%INPUT:Status%%Not Started%%In-Progress%%Complete%>%> <%NOBLOCKOUTPUT%>
    <%IFVAR:status,/.+/%><%SET:assignee,<%CURRENTUSER%>%> <%NOBLOCKOUTPUT%>
    <%IFVAR:assignee,/.+/%><%SET:created,<%DATE:today%>%> <%NOBLOCKOUTPUT%>
    <%IFVAR:created,/.+/%><%SET:due,<%INPUT:Due in how many days?%>%> <%NOBLOCKOUTPUT%>
    <%IFVAR:due,/.+/%><%SMARTBLOCK:Generate Project,<%GET:name%>%> <%NOBLOCKOUTPUT%>
    <%IFVAR:projectPage,/.+/%> <%SIDEBARWINDOWOPEN:<%GET:projectPage%>%><%TAG:<%GET:projectPage%>%>

Don’t copy/paste this into Roam! It’s a block-based tool; the workflow above is not in blocks. Click here to get the working SmartBlock.

Code explanation
  • 9-14: <%IFVAR:name,/.+/%>
    • The IFVAR commands that proceed each workflow prompt ensure that a variable (“name” in the case above) is not empty. If it is empty, stop running that block.

Step 4: Create our dynamic tasks table (default content)

As we defined in the outline, a tasks table should be created each time we create a new project. The table needs to capture all todo’s assigned to this specific project and output automatically each time a new one is created.

Below is a quick overview of how we accomplish that in Roam using Query Builder. In short, we define the table logic, replace hard-coded references with variables, and finally take our table code and incorporate it into our custom SmartBlock.

Confused? Don’t be, it’s actually quite easy. Let’s walk through each step:

1. Define the base logic for our tasks table

In this simple example, we are querying for all nodes that reference TODO and have a parent (represented by the variable parentNode) that references the Example Acme project. We input the logic for this query using Query Builder’s graphical interface, which auto-displays whenever we enter {{query block}} into a block in Roam.

2. Get the query instructions

The logic for our tasks table query from step 1 ends up as block-level instructions that output directly below the table (nested under “Results” and “Scratch”). These plain-text, block-level instructions define and process the query in Roam. Everything is contained within, from the query parameters, output style, view type, and even default filters/sorts.

3. Replace static instructions with variables

Now that we have our plain-text query instructions, we can use them to generate our default content. Notice the value for parentNode in step 1 is hard-coded “Example Acme project”? If we identify where that condition lives in the instructions and replace it with the (built-in) SmartBlock command <%CURRENTPAGENAME%>, we’ve now turned our static table into a dynamic table.

The final result: Supertag functionality in Roam Research

If it feels like it took a lot to get here, keep in the mind the following:

  • This was written for users with little-to-no experience with SmartBlocks or Query Builder.
  • As you get more comfortable using SmartBlocks and Query Builder, you can whip up custom workflows crazy fast. Building this workflow only took 10 minutes!
  • The final result is a blazing-fast workflow that outputs a new project page with additional fields and a dynamic tasks table in under 30 seconds 🤯

Visit The Roam Way graph to snag the Project SmartBlock and custom CSS from this article.

Our Project SmartBlock in Action

Roam’s SmartBlocks vs Tana Supertags – Which system is best?

I use Roam (you’re on a site called theroamway.com), but I’m going to be fair and as objective as possible about how the two platforms compare for this use case.

Setup: How easy is it to build and configure the supertag functionality?

Admittedly, the SmartBlock syntax in Roam looks daunting at first. That said, you don’t need to be a high-level programmer to implement SmartBlocks any more than you need to implement live searches in Tana. Both systems allow for the following:

  • Mandatory fields
  • Default content
  • Static and dynamic inputs

I’m giving Tana the slight edge in this match-up for two reasons: the supertag functionality is built-in to core and doesn’t require plugins. Tana’s interface is likely more approachable for the everyday user.

Winner: Tana

Features: Which system has the most complete feature-set?

Both systems can handle this project supertag and tasks-table use-case easily, but there are two feature sets unique to Tana that give them a leg-up:

  • Views
    • The ability and robustness of data querying are similar between the two systems. Tana stands out, however, in how well it handles the output of the data with its built-in views. They’re easy to use, have a highly polished design, and allow for functionality like post-query grouping that you don’t currently get in Roam.
    • You can make your Roam tables, lists, and kanbans pretty and highly functional, but it takes some tweaking and custom CSS to get there. Don’t believe me? Here’s a little sneak peek into my next post on building custom dashboards in Roam
  • Source data
    • Tana allows for importing data from external graphs, which is excellent if you want to keep things like custom definitions, schema, workflows, or private data separate from your working graph.

Winner: Tana

Flexibility – What system allows for the most customization?

Tana’s supertags have a lot of capabilities that cover probably 99% of use cases. With Tana, however, you are somewhat limited in what the tool allows you to do. In contrast, the Roam + SmartBlocks API will enable you to create, read, update or delete (CRUD) whatever you can imagine and build workflows and commands that are as simple or as complex as you want them to be. Tana’s more approachable (see above) and capable, but Roam + SmartBlocks is limitless.

Winner: Roam

Implementation – What system is the fastest and easiest to use?

When set up correctly, both systems are relatively easy to use, so this one is close to a draw. What gives Roam a slight leg-up is the natural language processing of dates. That will always be quicker than toggling over to a date picker. This is a 51/49 situation. Both systems are great and can be implemented without touching the mouse.

Winner: Roam

UX / UI – What’s it like interacting with the SmartBlock / Supertag workflow?

User experience is a difficult category to be objective about. So much of how you feel about a system comes down to personal preference. I personally like navigating through the workflow prompts in our custom SmartBlock (it’s fast, and I can do it all without touching the mouse), but others may gravitate towards editing inline in their graph. All that said, Tana takes the win here because Tana feels more polished and refined without any customizations.

Winner: Tana

Overall – Which system is best?

Both workflows are fast. Both allow for an insane level of customization and are backed by helpful and supportive communities. Overall, Tana has some features that, at this time, aren’t easily replicable in Roam (see the Features section above), and I love how refined and polished their default views are. It’s close, but they take the head-to-head by a slight margin (I bet you didn’t see that one coming).

Winner: Tana

Final thoughts

Despite what you might have thought when you read the title, this post wasn’t written to bash Tana (Hell, they won the head-to-head). That said, it’s easy to get lost in the hype of new tools. I’ve done it personally for Logseq, Obsidian, and Tana. Sometimes the hype has merit, and sometimes it takes a little digging to realize the tool you’re already using can do many of the same things. Sometimes the tool you’re already using can do even more, as you’ll see in future posts.

For those who are head-over-heels in love with Tana, I get it. It’s a fantastic tool with an engaged community. If, like me, you were on the fence about switching because you don’t think Roam can do “X’ thing and tool “Y” can – you should probably stick around and follow The Roam Way on Twitter. We have a lot more content like this coming your way.

Next up: Building custom Dashboards in Roam Research

This post took a while to put together, and I am immensely grateful to anyone who took the time to read it, engage with it or share it. I will talk to you again soon when we build a custom dashboard in Roam

Reader Interactions

Comments

  1. Oliver says

    Hello, I didn’t make it to the end of the process. Put me in the “dummies” category. Here is where I am.
    Step 1 done including saving the block reference for the client table. You indicate that it will be used in the next step but I didn’t understand where and at what precise step I should use it.
    Step 2 and 3 : done. I was able to create a new “Acme project”.
    Step 4.1: I created a query for the Acme project. So far, so good.
    4.2 : there is nothing to do at this step, correct? It’s just an explanation?
    4.3 : I’m still stuck at this step. I don’t understand the sentence: If we identify where that condition lives in the instructions and replace it with the (built-in) SmartBlock command , we’ve now turned our static table into a dynamic table.
    Which SmartBlock are you referring to? To one of those created previously or should we create one specific to the Acme project. Could you break-down the process attached to this phase?
    Thanks in advance for your explanations.

    • Chris says

      Hey Oliver,

      What we’re doing in step 4 is: manually building a query builder table, taking the output instructions from that manual build, and replacing the “hard-coded” parameters with SmartBlock variables, so it becomes a dynamic table.

      Click on the button link to The Roam Way public graph, then go to the Project SmartBlock link under table of contents. You can copy/paste that entire page into your graph, and it will work. The only thing you’ll need to change is the block-ref used in the <%QUERYBUILDER%> SmartBlock (replace sUWHCHyYp with the block ref of your clients table)

      • Oliver says

        Hey Chris,
        It works. Thanks!
        It would be nice if a task could be automatically listed in the AcmeProject TODO’s query by # it with the AcmeProject (#AcmeProject) rather than having to nest it under it.

Leave a Reply

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