Option card
A radio button as a whole card with a title, a line of detail and a price; for delivery and payment choices.
Install
If this is the first component in the project, set up once. This writes ermine.json and copies the theme stylesheet. The installation guide covers Vue, Nuxt and Astro.
npx ermineui initpnpm dlx ermineui inityarn dlx ermineui initbunx ermineui initThen add the component:
npx ermineui add option-cardpnpm dlx ermineui add option-cardyarn dlx ermineui add option-cardbunx ermineui add option-cardThat copies 2 files into src/components/ui/option-card/, and the 1 component it is built from. Files you already have are kept.
Use
import { OptionCard } from '@/components/ui/option-card'<RadioGroup v-model="delivery" aria-label="Delivery">
<OptionCard value="standard" title="Standard" description="3 to 5 working days"><template #end>Free</template></OptionCard>
<OptionCard value="express" title="Express" description="1 to 2 working days"><template #end>$12.00</template></OptionCard>
</RadioGroup>
<CartLineItem v-model:quantity="item.quantity" :title="item.title" :image="item.image" options="Sand / 350 ml" :price="24" :max="6" @remove="remove(item)" />
<OrderSummary :subtotal="144" :discount="10" discount-label="SAVE10" :shipping="0" :tax="11.52">
<template #footer><Button size="lg">Checkout</Button></template>
</OrderSummary>In Nuxt, with the auto-import setting, the tags work in templates without the import. In Astro, use it inside a Vue island.
What gets copied
| File | Where it goes |
|---|---|
OptionCard.vue | src/components/ui/option-card/OptionCard.vue |
index.ts | src/components/ui/option-card/index.ts |
It is built from: Radio group. These are added with it.
The folders are the suggested ones. ermine.json says where yours are.
Source
This is exactly what the command copies. To install by hand, create each file below and the helpers listed above.
OptionCard.vue 103 lines
<script setup lang="ts">
import { RadioGroupItem } from '../radio-group'
defineProps<{
value: string
title: string
/** A line of detail under the title. */
description?: string
disabled?: boolean
}>()
</script>
<template>
<!-- Goes inside a RadioGroup. The whole card is the radio button's label, so pressing anywhere on it chooses it. -->
<label class="er-option-card" :data-disabled="disabled ? '' : undefined">
<RadioGroupItem :value="value" :disabled="disabled" />
<span class="er-option-card-text">
<span class="er-option-card-title">{{ title }}</span>
<span v-if="description || $slots.default" class="er-option-card-description"><slot>{{ description }}</slot></span>
</span>
<!-- What it costs, or a logo. -->
<span v-if="$slots.end" class="er-option-card-end"><slot name="end" /></span>
</label>
</template>
<style>
@layer components {
.er-option-card {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.875rem 1rem;
border: 1px solid var(--er-input);
border-radius: var(--er-radius-md);
background: var(--er-input-background);
cursor: pointer;
transition:
background-color var(--er-duration),
border-color var(--er-duration),
box-shadow var(--er-duration);
}
.er-option-card:hover {
background: color-mix(in oklab, var(--er-accent) 60%, transparent);
}
/* The border thickens without moving anything: the extra pixel is drawn as a shadow. */
.er-option-card:has(.er-radio:checked) {
border-color: var(--er-primary);
box-shadow: 0 0 0 1px var(--er-primary);
}
.er-option-card:has(.er-radio:focus-visible) {
outline: 2px solid var(--er-ring);
outline-offset: 2px;
}
.er-option-card .er-radio {
margin-top: 0.125rem;
}
.er-option-card .er-radio:focus-visible {
outline: none;
}
.er-option-card-text {
display: grid;
flex: 1;
gap: 0.125rem;
min-width: 0;
}
.er-option-card-title {
font-size: 0.875rem;
font-weight: var(--er-weight-medium);
line-height: 1.25rem;
}
.er-option-card-description {
color: var(--er-muted-foreground);
font-size: 0.8125rem;
line-height: 1.25rem;
}
.er-option-card-end {
flex-shrink: 0;
font-size: 0.875rem;
font-weight: var(--er-weight-medium);
font-variant-numeric: tabular-nums;
line-height: 1.25rem;
}
.er-option-card[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.er-option-card[data-disabled]:hover {
background: var(--er-input-background);
}
}
</style>index.ts 3 lines
/** Option card — a radio button as a whole card with a title, a line of detail and a price; for delivery and payment choices. */
export { default as OptionCard } from './OptionCard.vue'