Skip to main content

Buttons

Buttons are interactive elements attached to messages. ChatOps4s makes buttons type-safe by associating each button handler with a value type.

Registering a Button Handler​

def registerButton(slack: SlackGateway[IO] & SlackSetup[IO]): IO[ButtonId[String]] =
slack.registerButton[String]("approve") { click =>
slack.update(click.messageId, s"Approved by ${click.userId.mention}").void
}

registerButton[T] returns a ButtonId[T] — a typed handle you use to create button instances. The type parameter T (which must be a subtype of String) is the type of value the button carries.

Rendering Buttons on Messages​

Use ButtonId[T].render to create Button instances and attach them to messages:

def renderButtons(
slack: SlackGateway[IO],
channel: String,
approveBtn: ButtonId[String],
rejectBtn: ButtonId[String],
): IO[Unit] =
slack
.send(
channel,
"Deploy v1.2.3 to production?",
Seq(
approveBtn.render("Approve", "v1.2.3"),
rejectBtn.render("Reject", "v1.2.3"),
),
)
.void

The first argument is the button label (displayed to the user), the second is the typed value passed to your handler when clicked.

Typed Button Values​

The type parameter on ButtonId and ButtonClick constrains what values a button can carry. The most common use is an opaque type that restricts the set of valid values:

opaque type Environment <: String = String
object Environment {
val Production: Environment = "production"
val Staging: Environment = "staging"
}
def constrainedButtons(slack: SlackGateway[IO] & SlackSetup[IO], channel: String): IO[Unit] =
for {
deployBtn <- slack.registerButton[Environment]("deploy-env") { click =>
// click.value is guaranteed to be a valid Environment
slack.update(click.messageId, s"Deploying to ${click.value}...").void
}
_ <- slack.send(
channel,
"Where to deploy?",
Seq(
deployBtn.render("Production", Environment.Production),
deployBtn.render("Staging", Environment.Staging),
),
)
} yield ()

This ensures only valid Environment values can be passed to render — the compiler prevents mistakes at the call site rather than at runtime.

Removing Buttons After Click​

A common pattern is to replace the buttons with a status message after a click:

def removeButtons(slack: SlackGateway[IO] & SlackSetup[IO]): IO[ButtonId[String]] =
slack.registerButton[String]("approve-and-remove") { click =>
// update replaces the message, removing buttons
slack.update(click.messageId, s"Approved by ${click.userId.mention}").void
}

update replaces the entire message content, including removing any buttons unless you pass new ones.