It’s certainly a style choice, but judicious use of mutually exclusive attributes can be ok. There is precedent in, for example, the <.link> component—you can give only one of patch, navigate, or href. Of course these aren’t booleans.
I do use booleans like this in one place with buttons. The button “variant” can be one of primary, secondary, or ghost (which is what we call “muted” or “tertiary”). I wasn’t super happy with calling the attribute variant, kind, or style, and type was out because that is already an HTML attribute for <button> that has a completely different meaning. So I went with mutually exclusive booleans.
I use the following code to raise if someone passes more than one:
def validate_boolean_group!(assigns, group) do
{attrs, booleans} =
assigns
|> Enum.filter(fn
{attr, _value} -> attr in group
_ -> false
end)
|> Enum.unzip()
num_true_values = Enum.count(booleans, & &1 == true)
if num_true_values <= 1 do
true
else
message = "You may only set one of: [:" <> Enum.join(attrs, ", :") <> "]"
raise DesignSystem.AttributeError, message: message
end
end
group is a list of possible values.
It’s definitely a smell that you have to add developer guard-rails. You also can’t use attr’s :default if you do this otherwise that check won’t work, so you have to set it in the component itself. This isn’t something I would use much at all, but in this one case I preferred the API. This is for my employer’s design system so depending on feedback, I may bail out of this decision anyway
Ya, sorry I thought it was implied that that is alternative—I have plenty of those. Just this one case I did indeed just like the vibe of the booleans.
Based on what you’ve described my vote is for C. Yes it is a bit aesthetically ugly but I think it makes it very clear that you’re passing a mutually exclusive enum that will be used directly within the elixir code (e.g. not something that gets directly passed down to html). For me clarity wins over aesthetics.