Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

slack-block-builder

Package Overview
Dependencies
Maintainers
1
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

slack-block-builder

Lightweight, zero-dependency library for creating Slack messages and modals, with a builder interface inspired by SwiftUI.

  • 0.1.3
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
77K
decreased by-0.31%
Maintainers
1
Weekly downloads
 
Created
Source

Logo

Maintainable code for Slack interactive messages, modals, and home tabs.

Lightweight, zero-dependency JavasScript library for Slack Block Kit UI.

View Real-World Examples »

Quick Start Guide · Request Feature · Report Bug

An example of using Block Builder


npm NPM codecov Maintainability

Block Builder helps you keep your Slack app code for UI maintainable, testable, and reusable. It has a simple builder syntax inspired by SwiftUI and lets you code the you want to code.

:zap:   Features

  • Simple SwiftUI-inspired syntax.
  • Builder syntax for better visual code structure.
  • Constructor interface that can be used together with the builder interface for maximum flexibility.
  • Support for all current Slack Block Kit objects – Surfaces, Blocks, Elements, and Composition Objects (View Support).
  • Super helpful JSDoc hints that include real-world explanations, Slack validation rules, and a direct link to the object's documentation on Slack's API doc site.
  • Output of the composed UI as either an object or JSON.
  • A printPreviewURL() method that outputs to the console a link to preview your UI on Slack's Block Kit Builder website.
  • Small size, with zero dependencies.

:rocket:   Coming Soon

  • In-depth doc site.
  • TypeScript type definitions.
  • Components, such as an Accordion module.
  • Configurable option to check Slack validation rules.
  • Guide for Slack apps with tips, tricks, and best practices.

:gift:   Benefits

  • Write three times less code.
  • Build more sophistocated, elegant flows.
  • Design better UI architecture (works as a template engine).
  • Focus more on experience and code in your IDE than on reading Slack API docs.
  • Code the way you want to code – not forced into any single paradigm.
  • Easily integrate localizations into your app.

:floppy_disk:   Installation

Using NPM:
npm install --save slack-block-builder
Using Yarn:
yarn add slack-block-builder

:space_invader:   Usage

Importing

At the top level of the library, there are a few objects exposed for import. You'll be using these to build out your UI:

import { Message, Blocks, Elements, Bits } from 'slack-block-builder';

You can also import, in place of Message, Modal or HomeTab.

Exposed Objects

Modal – Used to create a modal surface.

Message – Used to create a message surface.

HomeTab – Used to create a message surface.

Note that since you'll more often only be working with one surface per file, they are exposed individually, whereas the rest of the objects are grouped into categories.

Blocks – Layout blocks used to organize the UI.

Elements – UI elements that are used to capture user interaction.

Bits – These are composition objects from Slack's docs that are more focused on UI, not data structure (unlike text and filter objects). Included are Options, OptionGroup, and ConfirmationDialog. They felt like they were deserving of their own category.

Object Support and Reference

Below is a list of supported objects and how to access them in Block Builder:

TypeNameSupportAccess Via
SurfaceMessageMessage()
SurfaceModalModal()
SurfaceApp HomeAppHome()
BlockActionsBlocks.Actions()
BlockContextBlocks.Context()
BlockDividerBlocks.Divider()
BlockFileBlocks.File()
BlockImageBlocks.Image()
BlockInputBlocks.Input()
BlockSectionBlocks.Section()
ElementButton✅️Elements.Button()
ElementCheckboxesElements.Checkboxes()
ElementDate PickerElements.DatePicker()
ElementImageElements.Img()
ElementOverflow MenuElements.OverflowMenu()
ElementRadio ButtonsElements.RadioButtons()
ElementPlain Text InputElements.TextInput()
ElementSelect MenusElements.[Type]Select()
ElementMulti-Select MenusElements.[Type]MultiSelect()
Composition ObjectOptionBits.Option()
Composition ObjectConfirm DialogBits.ConfirmationDialog()
Composition ObjectOption GroupBits.OptionGroup()
Composition ObjectText ObjectBuilt in Background (Both Plain and Markdown)
Composition ObjectFilter ObjectBuilt in Background

Heirarchy/Structure

When building views, there'a certain heirarchy that needs to be followed, which is true to any library that works with Slack Blocks:

Surfaces => Contain Blocks – Think of these as the <body> HTML tag.

Blocks => Contain Elements – Think of these as the <section> or <div> HTML tags.

Elements => Contain Bits – More often than not, Elements are various types of input controls.

Bits are small pieces of the UI. Such as an Option for a select menu. However, the ConfirmationDialog Bit can be present in multiple areas of the UI, not just in Elements.

Creating a Simple Interactive Message

Let's take a look at how to compose an interactive message. Even though Slack now has modals, these have always been the basis for Slack apps.

We can create a piece of UI using only the builder methods:

import { Message, Blocks, Elements } from 'slack-block-builder';

const myMessage = ({ channel }) => {
  return Message()
    .channel(channel)
    .text('Alas, my friend.')
    .blocks(
      Blocks.Section()
        .text('One does not simply walk into Slack and click a button.'),
      Blocks.Section()
        .text('At least that\'s what my friend Slackomir said :crossed_swords:'),
      Blocks.Divider(),
      Blocks.Actions()
        .elements(
          Elements.Button()
            .text('Sure One Does')
            .actionId('gotClicked')
            .danger(),
          Elements.Button()
            .text('One Does Not')
            .actionId('scaredyCat')
            .primary()))
    .asUser()
    .buildToJSON();
};

Alternatively (and preferably), we can combine both the builder methods and the constructors to shorten it:

import { Message, Blocks, Elements } from 'slack-block-builder';

const myShorterMessage = ({ channel }) => {
  return Message({ channel, text: 'Alas, my friend.' })
    .blocks(
      Blocks.Section({ text: 'One does not simply walk into Slack and click a button.' }),
      Blocks.Section({ text: 'At least that\'s what my friend Slackomir said :crossed_swords:' }),
      Blocks.Divider(),
      Blocks.Actions()
        .elements(
          Elements.Button({ text: 'Sure One Does', actionId: 'gotClicked' })
            .danger(),
          Elements.Button({ text: 'One Does Not', actionId: 'scaredyCat' })
            .primary()))
    .asUser()
    .buildToJSON();
};

Both of these examples render the message below. And the best part? It only took 15 lines of code, as opposed to the 44 lines of JSON generated as a result.

An example of using Block Builder for Messages

View Example on Slack Block Kit Builder Website

If you look up at the example again, you can see there is more going on than just building out UI.

In this function, we are also defining the channel to which the message is sent, and also invoking the asUser() method. The Message and Modal objects in Block Builder both include similar methods.

If you've been working with the Slack API for a while, you know about these options already. More on that coming with the full documentation. In the meantime, check out the IDE hints and the rest of this README.

However, you'll definitely want to take a look at the Output section to get an idea of the methods used to get the final output.

Creating a Simple Modal

Let's take a look at how modals are created.

In this part, we'll also take a look at working with Bits. You'll see in this example that we're hardcoding the options in the select menu. There are, of course, better ways to handle that. That will be covered a bit later in the About The Builder Methods section.

First, an example using just our builder methods:

import { Modal, Blocks, Elements, Bits } from 'slack-block-builder';

const myModal = () => {
  return Modal()
    .title('PizzaMate')
    .blocks(
      Blocks.Section()
        .text('Hey there, colleague!'),
      Blocks.Section()
        .text('Hurray for corporate pizza! Let\'s get you fed and happy :pizza:'),
      Blocks.Input()
        .label('What can we call you?')
        .element(
          Elements.TextInput()
            .placeholder('Hi, my name is... (What?!) (Who?!)')
            .actionId('name')),
      Blocks.Input()
        .label('Which floor are you on?')
        .element(
          Elements.TextInput()
            .placeholder('HQ – Fifth Floor')
            .actionId('floor')),
      Blocks.Input()
        .label('What\'ll you have?')
        .element(
          Elements.StaticSelect()
            .placeholder('Choose your favorite...')
            .actionId('item')
            .options(
              Bits.Option().text(':cheese_wedge: With Cheeze').value('012'),
              Bits.Option().text(':fish: With Anchovies').value('013'),
              Bits.Option().text(':cookie: With Scooby Snacks').value('014'),
              Bits.Option().text(':beer: I Prefer Steak and Beer').value('015'))))
    .submit('Get Fed')
    .buildToJSON();
};

Alternatively (and preferably), we can combine both the builder methods and the constructors to shorten it:

import { Modal, Blocks, Elements, Bits } from 'slack-block-builder';

const myShorterModal = () => {
  return Modal({ title: 'PizzaMate', submit: 'Get Fed' })
    .blocks(
      Blocks.Section({ text: 'Hey there, colleague!' }),
      Blocks.Section({ text: 'Hurray for corporate pizza! Let\'s get you fed and happy :pizza:' }),
      Blocks.Input({ label: 'What can we call you?' })
        .element(
          Elements.TextInput({ placeholder: 'Hi, my name is... (What?!) (Who?!)' })
            .actionId('name')),
      Blocks.Input({ label: 'Which floor are you on?' })
        .element(
          Elements.TextInput({ placeholder: 'HQ – Fifth Floor' })
            .actionId('floor')),
      Blocks.Input({ label: 'What\'ll you have?' })
        .element(
          Elements.StaticSelect({ placeholder: 'Choose your favorite...' })
            .actionId('item')
            .options(
              Bits.Option({ text: ':cheese_wedge: With Cheeze', value: '012' }),
              Bits.Option({ text: ':fish: With Anchovies', value: '013' }),
              Bits.Option({ text: ':cookie: With Scooby Snacks', value: '014' }),
              Bits.Option({ text: ':beer: I Prefer Steak and Beer', value: '015' }))))
    .buildToJSON();
};

Both of these examples render the modal below.

An example of using Block Builder for Modals

View Example on Slack Block Kit Builder Website

Similar to the Message object, the Modal object also supports non UI-related options, such as notifyOnClose() or clearOnClose(). But those will be covered in the full documentation.

However, you'll definitely want to take a look at the Output section to get an idea of the methods used to get the final output.

The above examples should give you a general idea of how to use Block Builder, so we'll skip creating a HomeTab object, as it almost identical to building out a Slack modal.

About The Builder Methods

Using the builder methods is super straightforward, but there are a few things that you should know and consider before starting.

All Builder Methods Accept undefined

If you pass an argument that is undefined, you will not get an error. Instead, the property will simply not be set. And it's a feature, not a bug.

To learn more as to why that is, see the Advanced Use Cases section.

Some Methods Set, Some Append

Certain builder methods set a single value, whereas others append a new value to an array of existing values.

As a general rule – methods that end in an 's' will append. A few examples are Modal.blocks(), Blocks.Actions.elements(), and Elements.StaticSelect.options().

And as such...

Methods that set can only be called once.


const myModal = () => {
  return Modal()
    .title('My Modal')  // Sets the title of the modal
    .blocks( /* Imagine Blocks */ )
    .title('My Modal')  // Will throw an error, as it has already been set through the 'title()' method
    .buildToJSON();
};

This also applies to properties that have been set in the constructor.

const myOtherModal = () => {
  return Modal({ title: 'My Other Modal' })
    .title('My Modal')  // Will throw an error, as it has already been set through the constructor
    .blocks( /* Imagine blocks */ )
    .buildToJSON();
};

Methods that append can be called multiple times.

const myModal = () => {
  return Modal({ title: 'My Other Modal' })
    .blocks( /* Add some block */ )
    .blocks( /* Add another */ ) // Does not throw error, simply appends the Block object
    .buildToJSON();
};
Methods That Append Accept Arrays and Multiple Args

When using a method that appends a value, or a set of values, you can pass in:

  • A single argument.
  • Multiple arguments.
  • An array.

This way, you can code however you want, withought being forced into a certain paradigm by Block Builder.

All three of the following examples produce the exact same result:

// Passing a single argument

const myModal = () => {
  return Modal({ title: 'Single Arg Example' })
    .blocks(
      Blocks.Section({ text: 'This is the first section.' }))
    .blocks(
      Blocks.Section({ text: 'This is the second section.' }))
    .blocks(
      Blocks.Section({ text: 'This is the third section.' }))
    .buildToJSON();
};
// Passing multiple arguments

const myModal = () => {
  return Modal({ title: 'Multiple Args Example' })
    .blocks(
      Blocks.Section({ text: 'This is the first section.' }))
    .blocks(
      Blocks.Section({ text: 'This is the second section.' }),
      Blocks.Section({ text: 'This is the third section.' }))
    .buildToJSON();
};
// Passing an array

const myModal = () => {
  return Modal({ title: 'Multiple Args Example' })
    .blocks(
      Blocks.Section({ text: 'This is the first section.' }))
    .blocks([
      Blocks.Section({ text: 'This is the second section.' }),
      Blocks.Section({ text: 'This is the third section.' }),
    ])
    .buildToJSON();
};

This also allows you to use helpful functions such as Array.map(), Array.reduce(), etc.

Returning to our modal example from above, we can use this to programmatically create the list of options:

const myShorterModal = ({ menu }) => { // Pass in an array of menu items from data source
  return Modal({ title: 'PizzaMate', submit: 'Get Fed' })
    .blocks(
      Blocks.Section({ text: 'Hey there, colleague!' }),
      Blocks.Section({ text: 'Hurray for corporate pizza! Let\'s get you fed and happy :pizza:' }),
      Blocks.Input({ label: 'What can we call you?' })
        .element(
          Elements.TextInput({ placeholder: 'Hi, my name is... (What?!) (Who?!)' })
            .actionId('name')),
      Blocks.Input({ label: 'Which floor are you on?' })
        .element(
          Elements.TextInput({ placeholder: 'HQ – Fifth Floor' })
            .actionId('floor')),
      Blocks.Input({ label: 'What\'ll you have?' })
        .element(
          Elements.StaticSelect({ placeholder: 'Choose your favorite...' })
            .actionId('item')
            .options(menu.map((item) => Bits.Option({ text: item.name, value: item.id }))))) // Map items to Option objects
    .buildToJSON();
};

Output

There are a few different methods that can be called on surfaces (Message, Modal, HomeTab) to get various types of output:

Message.buildToJSON()
Modal.buildToJSON()
HomeTab.buildToJSON()

Returns a JSON (string) representation of the UI, compatible with the Slack API.

Message.buildToObject()
Modal.buildToObject()
HomeTab.buildToObject()

Returns an object representation of the UI, compatible with the Slack API (once stringified).

Message.printPreviewUrl()
Modal.printPreviewUrl()
HomeTab.printPreviewUrl()

Logs a preview URL to Slack's Block Kit Builder website to the console.

Message.getBlocks()

Returns an array of Block objects attached to the Message object. Use this if you wish to move all message configuration to another area of your application.

Note that only one method can be called on the object, and needs to be called after all of the necessary properties have been set.

Advanced Use Cases

Using Conditionals

There are multiple ways to handle conditionals with Block Builder. This is where Block Builder provides the most flexibility, since the builder methods, when receiving an argument that is undefined, simply do not set the property.

While you can definitely use traditional if statements, you can also pass in potentially undefined values, inline ternary expressions, the && operator, or even self-invoking functions. Below there is a real-world example, but let's first take a look at the basics:

import { Modal, Blocks, Elements, Bits } from 'slack-block-builder';

const someModal = ({ someData }) => {
  return Modal({ title: 'Using If Statements' })
    .blocks(
      Blocks.Section({ text: 'Let\'s take a look at conditionals.' }),
      Blocks.Section({ text: 'This is a much better way to do it!' }))
    .blocks(someData && [
      Blocks.Section({ text: 'This is added if condition is true.' }),
      Blocks.Section({ text: 'And it\'s super easy and readable!' }),
    ])
    .submit(someData ? 'Is Is True' : 'It Is False')
    .buildToJSON();
};

As you can see, doing this helps keep the view-like structure of the file, while taking full advantage of conditionals.

However, you can definitely use traditional if statements:

import { Modal, Blocks, Elements, Bits } from 'slack-block-builder';

const someModal = ({ someData }) => {
  const view = Modal({ title: 'Using If Statements' })
    .blocks(
      Blocks.Section({ text: 'Let\'s take a look at using an if statement.' }),
      Blocks.Section({ text: 'It\'s actually quite simple.' }))
  
  if (someData) {
    view.blocks( /* Append Blocks */ );
  }
  
  if (someData) {
    view.submit('It Is True');
  } else {
    view.submit('It Is False');
  }

  return view.buildToJSON();
};

When it comes to appending blocks or changing content for a surface, this is perfectly fine. But the code becomes much less declarative once those conditions start to apply to nested objects, as you'll end up moving code to the top of the function:

import { Modal, Blocks, Elements, Bits } from 'slack-block-builder';

const someModal = ({ someData }) => {
  const someBlock = Blocks.Section()
  
  someData 
    ? someBlock.text('Can get unintuitive with nested objects.')
    : someBlock.text('But there is a much better way!')
  
  const view = Modal({ title: 'Using If Statements' })
    .blocks(
      Blocks.Section({ text: 'Let\'s take a look at using an if statement.' }),
      someBlock,
    )
  
  if (someData) {
    view.blocks( /* Append Blocks */ );
  }
  
  if (someData) {
    view.submit('It Is True');
  } else {
    view.submit('It Is False');
  }

  return view.buildToJSON();
};

:mag:   Real-World Examples

Conditionals

Let's say you have an app that manages team rosters, through a Slack modal.

When it opens, it greets the user and presents them with a select menu of teams:

An example of using Block Builder

View Example on Slack Block Kit Builder Website

When the user selects a team from the menu, a multiselect is appended to the modal, prepopulated with all of the team's members. In addition, the Save Changes button appears:

An example of using Block Builder

View Example on Slack Block Kit Builder Website

This flow can be called in two different ways:

  • Via a slash command /teams
  • Via a button called Edit Bot Squadin an interactive message sent to one of the members.

In the first case, you don't know which team the user wants to edit, so the modal should open with no default option in the select menu, and no appended multiselect menu for the members, either.

However, in the second case, you know exactly which team the user wants to edit, so the modal should open up with the selected team and the team's members. We are counting clicks, right?

Let's try it out with some inline conditionals and see how it reads:

import { Modal, Blocks, Elements, Bits } from 'slack-block-builder';

const editTeamModal = ({ teams, selectedTeamId, members }) => {
  return Modal({ title: 'Edit Team' })
    .callbackId('editTeam')
    .blocks(
      Blocks.Section({ text: 'Hey there! Let\'s make sure our teams are up to date!' }),
      Blocks.Input({ label: 'Which team do you want to edit?' })
        .element(
          Elements.StaticSelect({ placeholder: 'Select a team...' })
            .actionId('team')
            .options(teams.map((team) => Bits.Option({ text: team.name, value: team.id })))
            .initialOption(selectedTeamId && teams
              .filter((team) => team.id === selectedTeamId) // Only include initial option is team has been chosen
              .map((team) => Bits.Option({ text: team.name, value: team.id }))[0]
            )))
    .blocks(selectedTeamId && [ // Only include multiselect for members if team has been selected
      Blocks.Divider(),
      Blocks.Input({ label: 'Team Members' })
        .element(
          Elements.UserMultiSelect({ placeholder: 'Please add members...' })
            .actionId('members')
            .initialUsers(members && members.map((member) => member.slackId))), // Only include initials members if members are present
    ])
    .submit(selectedTeamId && 'Save Changes') // Only include 'Save Changes' button if form has actually been loaded
    .buildToJSON();
};

There are multiple ways to add logic with this in mind. If you're feeling especially adventurous, you could even pass in a self-invoking function with a switch block.

Originally, the idea of accepting a value of undefined was to help assist with simple things, such as passing in a value to the initialOption() method for select menus, but it opens the door for a lot manipulation. It's up to you to decide where you draw the line with this type of inline logic.

Bolt for Javascript – A simple framework for building Slack apps, developed by Slack themselves.

Node Slack SDK – A great and powerful SDK for building Slack Apps from the ground up.

:fire:   Acknowledgements

@bravecow Taras Neporozhniy (@bravecow) - For all of the advice!

@ft502 Alexey Chernyshov (@ft502 on Dribbble) - For such a beautiful logo!

@slackhq SlackHQ (@slackhq) - For such a wonderful product and API!

:black_nib:   Author

@raycharius Ray East (@raycharius) - Huge Fan of Slack and Block Builder Maintainer

Keywords

FAQs

Package last updated on 12 Jun 2020

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc