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] { click =>
slack.update(click.messageId, s"Approved by <@${click.userId}>").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] { 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] { click =>
// update replaces the message, removing buttons
slack.update(click.messageId, s"Approved by <@${click.userId}>").void
}
update replaces the entire message content, including removing any buttons unless you pass new ones.