Radio Card
Selectable card component that uses radio buttons for single-option selection.
TailwindCSS
package showcase
import "github.com/axzilla/templui/components"
import "github.com/axzilla/templui/icons"
templ RadioCardDefault() {
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
@components.RadioCard(components.RadioCardProps{
ID: "comp-plan-basic",
Name: "comp-plan",
Value: "basic",
}) {
@components.RadioCardHeader() {
<div class="flex items-center gap-2">
@icons.Package(icons.IconProps{Size: 20})
<h3>Basic Plan</h3>
</div>
}
@components.RadioCardDescription() {
Essential features for individuals and small teams
}
@components.RadioCardFooter() {
@radioCardPriceFooter("$5.99")
}
}
@components.RadioCard(components.RadioCardProps{
ID: "comp-plan-pro",
Name: "comp-plan",
Value: "pro",
}) {
@components.RadioCardHeader() {
<div class="flex items-center gap-2">
@icons.Star(icons.IconProps{Size: 20})
<h3>Pro Plan</h3>
</div>
}
@components.RadioCardDescription() {
Enhanced capabilities for growing businesses.
}
@components.RadioCardFooter() {
@radioCardPriceFooter("$14.99")
}
}
@components.RadioCard(components.RadioCardProps{
ID: "comp-plan-enterprise",
Name: "comp-plan",
Value: "enterprise",
Disabled: true,
}) {
@components.RadioCardHeader() {
<div class="flex items-center gap-2">
@icons.Building(icons.IconProps{Size: 20})
<h3>Enterprise Plan</h3>
</div>
}
@components.RadioCardDescription() {
Advanced features for large organizations
}
@components.RadioCardFooter() {
@radioCardPriceFooter("$29.99")
}
}
</div>
}
templ radioCardPriceFooter(price string) {
<div class="flex justify-between items-center border-t border-border pt-2 mt-2 text-sm">
<span class="text-muted-foreground">Price</span>
<span class="font-medium">{ price }</span>
</div>
}
package components
import "github.com/axzilla/templui/utils"
type RadioCardProps struct {
ID string
Class string
Attributes templ.Attributes
Name string
Value string
Checked bool
Disabled bool
Required bool
}
type RadioCardHeaderProps struct {
ID string
Class string
Attributes templ.Attributes
}
type RadioCardDescriptionProps struct {
ID string
Class string
Attributes templ.Attributes
}
type RadioCardFooterProps struct {
ID string
Class string
Attributes templ.Attributes
}
templ RadioCard(props ...RadioCardProps) {
{{ var p RadioCardProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
if p.ID == "" {
{{ p.ID = utils.RandomID() }}
}
<div
id={ p.ID + "-container" }
class={
utils.TwMerge(
"relative",
utils.If(p.Disabled, "opacity-60"),
p.Class,
),
}
{ p.Attributes... }
>
<input
type="radio"
id={ p.ID }
if p.Name != "" {
name={ p.Name }
}
if p.Value != "" {
value={ p.Value }
}
checked?={ p.Checked }
disabled?={ p.Disabled }
required?={ p.Required }
class="peer sr-only"
/>
<label
for={ p.ID }
class={
utils.TwMerge(
"block w-full rounded-lg border overflow-hidden h-full",
"bg-card text-card-foreground p-4 flex flex-col",
"cursor-pointer",
"hover:border-primary/50",
"peer-checked:ring-1 peer-checked:ring-primary peer-checked:border-primary",
utils.If(p.Disabled, "cursor-not-allowed"),
"transition-all duration-200",
p.Class,
),
}
{ p.Attributes... }
>
{ children... }
</label>
</div>
}
templ RadioCardHeader(props ...RadioCardHeaderProps) {
{{ var p RadioCardHeaderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<div
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("flex items-center justify-between mb-2", p.Class) }
{ p.Attributes... }
>
{ children... }
</div>
}
templ RadioCardDescription(props ...RadioCardDescriptionProps) {
{{ var p RadioCardDescriptionProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<p
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("text-sm text-muted-foreground", p.Class) }
{ p.Attributes... }
>
{ children... }
</p>
}
templ RadioCardFooter(props ...RadioCardFooterProps) {
{{ var p RadioCardFooterProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<div
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("mt-auto pt-4 w-full", p.Class) }
{ p.Attributes... }
>
{ children... }
</div>
}
Usage
@components.RadioCard(components.RadioCardProps{...})