Button
A button with the shadcn variants and sizes.
<button.Button>Button</button.Button>Installation
Run the command in the app module.
gx add buttonThe command also installs icons.
The command writes these files.
| File | Path in the app |
|---|---|
Button.gx |
ui/button/Button.gx |
Button.fixtures.go |
ui/button/Button.fixtures.go |
styles.go |
ui/button/styles.go |
Install icons first.
Copy each file to its path in the app. Change each import of a registry package to the path of that package in the app.
package button
props {
// Variant sets the visual style: Default, Secondary, Destructive, Outline, Ghost or Link.
Variant Variant = Default
// Size sets the height and padding: Xs, Sm, Md, Lg, or an Icon size for a square button.
Size Size = Md
// Type is the type attribute: "button", "submit" or "reset".
Type string = "button"
// Class adds classes to the root element.
Class string = ""
// Children is the button label.
Children gx.Node
// Attrs adds HTML attributes to the root element.
Attrs gx.Attrs = nil
}
<button data-slot="button" data-variant={p.variant()} data-size={p.size()} type={p.typeAttr()} class={Class(p.Variant, p.Size, p.Class)} {...p.Attrs}>{p.Children}</button>package button
import (
"github.com/alternayte/gx"
"github.com/alternayte/gx/registry/icons"
)
// iconLabel is the accessible name of an icon-only fixture.
var iconLabel = gx.Attrs{{Key: "aria-label", Value: "Next"}}
var ButtonFixtures = gx.Fixtures[ButtonProps]{
"Default": {Children: gx.Text("Button")},
"Secondary": {Variant: Secondary, Children: gx.Text("Secondary")},
"Destructive": {Variant: Destructive, Children: gx.Text("Delete")},
"Outline": {Variant: Outline, Children: gx.Text("Outline")},
"Ghost": {Variant: Ghost, Children: gx.Text("Ghost")},
"Link": {Variant: Link, Children: gx.Text("Link")},
"ExtraSmall": {Size: Xs, Children: gx.Text("Extra small")},
"Small": {Size: Sm, Children: gx.Text("Small")},
"Large": {Size: Lg, Children: gx.Text("Large")},
"WithIcon": {Variant: Outline, Children: gx.Frag(gx.Text("Next"), icons.ChevronRight(icons.ChevronRightProps{}))},
"Icon": {Variant: Outline, Size: Icon, Attrs: iconLabel, Children: icons.ChevronRight(icons.ChevronRightProps{})},
"IconXs": {Variant: Outline, Size: IconXs, Attrs: iconLabel, Children: icons.ChevronRight(icons.ChevronRightProps{})},
"IconSm": {Variant: Outline, Size: IconSm, Attrs: iconLabel, Children: icons.ChevronRight(icons.ChevronRightProps{})},
"IconLg": {Variant: Outline, Size: IconLg, Attrs: iconLabel, Children: icons.ChevronRight(icons.ChevronRightProps{})},
"Disabled": {Attrs: gx.Attrs{gx.Bool("disabled", true)}, Children: gx.Text("Disabled")},
}package button
import "github.com/alternayte/gx"
// Variant is the visual style of a button.
type Variant string
// The variants of button.Button.
const (
Default Variant = "default"
Secondary Variant = "secondary"
Destructive Variant = "destructive"
Outline Variant = "outline"
Ghost Variant = "ghost"
Link Variant = "link"
)
var variantClass = gx.Enum[Variant]{
Default: "bg-primary text-primary-foreground hover:bg-primary/90",
Secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
Destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
Outline: "border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
Ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
Link: "text-primary underline-offset-4 hover:underline",
}
// Size is the height and padding of a button.
type Size string
// The sizes of button.Button.
const (
Xs Size = "xs"
Sm Size = "sm"
Md Size = "default"
Lg Size = "lg"
Icon Size = "icon"
IconXs Size = "icon-xs"
IconSm Size = "icon-sm"
IconLg Size = "icon-lg"
)
var sizeClass = gx.Enum[Size]{
Xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
Sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
Md: "h-9 px-4 py-2 has-[>svg]:px-3",
Lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
Icon: "size-9",
IconXs: "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
IconSm: "size-8",
IconLg: "size-10",
}
// typeAttr returns the button type; a zero value is "button".
func (p ButtonProps) typeAttr() string {
if p.Type == "" {
return "button"
}
return p.Type
}
// variant returns the data-variant value; a zero value is Default.
func (p ButtonProps) variant() string {
if p.Variant == "" {
return string(Default)
}
return string(p.Variant)
}
// size returns the data-size value; a zero value is Md.
func (p ButtonProps) size() string {
if p.Size == "" {
return string(Md)
}
return string(p.Size)
}
// Class returns the classes of one button state. A link that looks like a
// button takes these classes on its anchor.
func Class(variant Variant, size Size, extra ...string) string {
const base = "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
// A zero-value prop is the default, so a Go caller and a .gx caller
// render the same markup.
if variant == "" {
variant = Default
}
if size == "" {
size = Md
}
return gx.Cx(append([]string{base, variantClass[variant], sizeClass[size]}, extra...)...)
}The theme must define these tokens: --primary, --primary-foreground, --secondary, --secondary-foreground, --destructive, --accent, --accent-foreground, --background, --border, --input, --ring.
Usage
A clickable control with variant and size options.
<button.Button>Save</button.Button>
<button.Button variant={button.Destructive}>Delete</button.Button>
<button.Button variant={button.Outline} size={button.Sm}>Cancel</button.Button>
<button.Button variant={button.Ghost} size={button.Icon} attrs={gx.Attrs{{Key: "aria-label", Value: "Close"}}}>
<icons.X />
</button.Button>Pass Type when the button submits a form: <button.Button type="submit">Send</button.Button>.
The sizes are Xs, Sm, Md, Lg, Icon, IconXs, IconSm and IconLg.
An icon without a size- class takes the size of the button.
A link that looks like a button takes button.Class on its anchor: <a href={route} class={button.Class(button.Outline, button.Sm)}>Docs</a>.
Examples
Default
<button.Button>Button</button.Button>Secondary
<button.Button variant={button.Secondary}>Secondary</button.Button>Destructive
<button.Button variant={button.Destructive}>Delete</button.Button>Outline
<button.Button variant={button.Outline}>Outline</button.Button>Ghost
<button.Button variant={button.Ghost}>Ghost</button.Button>Link
<button.Button variant={button.Link}>Link</button.Button>Extra small
<button.Button size={button.Xs}>Extra small</button.Button>Small
<button.Button size={button.Sm}>Small</button.Button>Large
<button.Button size={button.Lg}>Large</button.Button>With icon
<button.Button variant={button.Outline}>
Next
<icons.ChevronRight />
</button.Button>Icon
<button.Button variant={button.Outline} size={button.Icon} attrs={gx.Attrs{{Key: "aria-label", Value: "Next"}}}>
<icons.ChevronRight />
</button.Button>Icon xs
<button.Button variant={button.Outline} size={button.IconXs} attrs={gx.Attrs{{Key: "aria-label", Value: "Next"}}}>
<icons.ChevronRight />
</button.Button>Icon sm
<button.Button variant={button.Outline} size={button.IconSm} attrs={gx.Attrs{{Key: "aria-label", Value: "Next"}}}>
<icons.ChevronRight />
</button.Button>Icon lg
<button.Button variant={button.Outline} size={button.IconLg} attrs={gx.Attrs{{Key: "aria-label", Value: "Next"}}}>
<icons.ChevronRight />
</button.Button>Disabled
<button.Button attrs={gx.Attrs{gx.Bool("disabled", true)}}>Disabled</button.Button>API reference
A tag sets a prop by its name with a lower-case first letter: Class is class.
button.Button
| Prop | Type | Default | Description |
|---|---|---|---|
Variant |
Variant |
Default |
Variant sets the visual style: Default, Secondary, Destructive, Outline, Ghost or Link. |
Size |
Size |
Md |
Size sets the height and padding: Xs, Sm, Md, Lg, or an Icon size for a square button. |
Type |
string |
"button" |
Type is the type attribute: "button", "submit" or "reset". |
Class |
string |
"" |
Class adds classes to the root element. |
Children |
gx.Node |
Required | Children is the button label. |
Attrs |
gx.Attrs |
nil |
Attrs adds HTML attributes to the root element. |
Do and do not
Do
- Use one
defaultbutton per view for the main action. - Set
type="submit"on a form button. The defaultbuttontype does not submit. - Give an icon-only button an
aria-label.
Don't
- Do not use a button as a link. Use an
<a>with a typedhrefandbutton.Class. - Do not put a button inside another button.
Keyboard
| Key | Action |
|---|---|
| Tab | Moves focus to the button. |
| Enter | Activates the button. |
| Space | Activates the button. |