PricingTable
Plans across and features down, with ticks, dashes and values in the cells and the recommended plan marked.
Demo
| Usage | |||
|---|---|---|---|
| Projects | 3 | Unlimited | Unlimited |
| Storage | 1 GB | 50 GB | 1 TB |
| Custom domain | Not included | Included | Included |
| Support | |||
| Community forum | Included | Included | Included |
| Priority supportAnswered within a day | Not included | Included | Included |
| Dedicated manager | Not included | Not included | Included |
Get it
It needs theme.qwe, as every component does, and price.qwe; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/price.qwe$ curl -O https://quillolang.org/components/ui/pricing_table.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/pricing_table.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/pricing_table.qwe
view PricingTableDemo() = <div.demo-stack> <PricingTable plans={[ { name: "Hobby", price: 0, period: "month", href: "#pricing_table", button: "Start free" }, { name: "Pro", price: 12, old: 15, period: "month", note: "billed yearly", href: "#pricing_table", recommended: true }, { name: "Team", price: 49, period: "month", note: "for 5 people", href: "#pricing_table" }]} features={[ { group: "Usage" }, { label: "Projects", values: ["3", "Unlimited", "Unlimited"] }, { label: "Storage", values: ["1 GB", "50 GB", "1 TB"] }, { label: "Custom domain", values: [false, true, true] }, { group: "Support" }, { label: "Community forum", values: [true, true, true] }, { label: "Priority support", hint: "Answered within a day", values: [false, true, true] }, { label: "Dedicated manager", values: [false, false, true] }]}/> </div>Source
ui/pricing_table.qwe
// PricingTable: plans across the top, features down the side, a tick, a dash or// a value in each cell, with the recommended plan marked. Prices are written as// Price writes them (this file needs price.qwe).//// <PricingTable plans={[// { name: "Hobby", price: 0, period: "month", href: "/signup?plan=hobby", button: "Start free" },// { name: "Pro", price: 12, period: "month", href: "/signup?plan=pro", recommended: true },// { name: "Team", price: 49, period: "month", href: "/signup?plan=team" }]}// features={[// { group: "Usage" },// { label: "Projects", values: ["3", "Unlimited", "Unlimited"] },// { label: "Storage", values: ["1 GB", "50 GB", "1 TB"] },// { group: "Support" },// { label: "Priority support", hint: "Answered within a day", values: [false, true, true] }]}/>//// plans: a list of { name, price, period?, old?, note?, href?, button?,// recommended? }: price a number or text; period as for PriceTag ("month");// note a line under the price ("billed yearly"); href makes the button a link// (button: its text, "Choose"); recommended marks the column, with// recommended_label ("Recommended") above it. features: rows of { label,// hint?, values } with one value per plan (true a tick, false a dash, any text// as it is), or { group } for a heading between rows. currency, locale, cents// (false) as for Price. caption: what screen readers call the table ("Plans").// On narrow screens the table scrolls sideways.import ui/price.qwe use price_textimport ui/theme.qwe use ui_clslet pricing_table_css = css`.ui-pricing-wrap { max-width: 100%; overflow-x: auto; }.ui-pricing { width: 100%; border-collapse: separate; border-spacing: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-pricing caption { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }.ui-pricing th, .ui-pricing td { padding: 10px 14px; border-bottom: 1px solid var(--ui-border); text-align: left; vertical-align: top; }.ui-pricing thead th { vertical-align: bottom; padding-bottom: 14px; }.ui-pricing tbody th { font-weight: 500; }.ui-pricing td { text-align: center; }.ui-pricing-plan { display: grid; gap: 4px; justify-items: center; text-align: center; min-width: 140px; }.ui-pricing-name { font-size: 16px; font-weight: 600; }.ui-pricing-price { display: inline-flex; align-items: baseline; gap: 4px; font-size: 24px; font-weight: 700; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }.ui-pricing-period { font-size: 13px; font-weight: 400; color: var(--ui-muted); }.ui-pricing-old { font-size: 14px; font-weight: 400; color: var(--ui-muted); text-decoration: line-through; }.ui-pricing-note { font-size: 12px; font-weight: 400; color: var(--ui-muted); }.ui-pricing-btn { display: inline-flex; align-items: center; justify-content: center; height: 34px; margin-top: 6px; padding: 0 16px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: 500 13px/1 var(--ui-font); text-decoration: none; cursor: pointer; }.ui-pricing-btn:hover { border-color: var(--ui-strong); background: var(--ui-subtle); }.ui-pricing-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-pricing-rec .ui-pricing-btn { background: var(--ui-accent); color: var(--ui-accent-fg); border-color: transparent; }.ui-pricing-rec .ui-pricing-btn:hover { background: color-mix(in srgb, var(--ui-accent) 88%, var(--ui-fg)); }.ui-pricing-rec { background: color-mix(in srgb, var(--ui-accent-soft) 45%, var(--ui-surface)); }.ui-pricing-rec-tag { display: inline-block; margin-bottom: 6px; padding: 2px 9px; border-radius: 999px; background: var(--ui-accent); color: var(--ui-accent-fg); font: 600 11px/1.6 var(--ui-font); text-transform: uppercase; letter-spacing: .04em; }.ui-pricing-group th { padding-top: 18px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--ui-muted); border-bottom: 0; }.ui-pricing-hint { display: block; font-size: 12px; font-weight: 400; color: var(--ui-muted); }.ui-pricing-yes { color: var(--ui-success); }.ui-pricing-yes svg { width: 18px; height: 18px; vertical-align: -4px; }.ui-pricing-no { color: var(--ui-strong); }.ui-pricing tbody tr:last-child th, .ui-pricing tbody tr:last-child td { border-bottom: 0; }`view PricingTable(plans: list = [], features: list = [], currency: str = "USD", locale: str? = null, cents: bool = false, caption: str = "Plans", recommended_label: str = "Recommended", class: str? = null) { style global pricing_table_css let rec = (p) => get(p, "recommended") == true <div class={ui_cls(["ui-pricing-wrap", class])}> <table.ui-pricing> <caption>{caption}</caption> <thead> <tr> <td></td> {for p in plans => <th scope="col" key={p.name} class={rec(p) ? "ui-pricing-rec" : null}> <div.ui-pricing-plan> {rec(p) && <span.ui-pricing-rec-tag>{recommended_label}</span>} <span.ui-pricing-name>{p.name}</span> <span.ui-pricing-price> {get(p, "old") != null && <s.ui-pricing-old><span.ui-sr>was </span>{price_text(p.old, currency, locale, cents)}</s>} {price_text(p.price, currency, locale, cents)} {get(p, "period") != null && <span.ui-pricing-period><span.ui-sr>per {p.period}</span><span aria-hidden="true">/{p.period}</span></span>} </span> {get(p, "note") != null && <span.ui-pricing-note>{p.note}</span>} {get(p, "href") != null && <a.ui-pricing-btn href={p.href}>{get(p, "button") ?? "Choose"}<span.ui-sr> {p.name}</span></a>} </div> </th>} </tr> </thead> <tbody> {for [i, f] in enumerate(features) => get(f, "group") != null ? <tr.ui-pricing-group key={`g${i}`}><th scope="colgroup" colspan={len(plans) + 1}>{f.group}</th></tr> : <tr key={f.label}> <th scope="row">{f.label}{get(f, "hint") != null && <span.ui-pricing-hint>{f.hint}</span>}</th> {for [j, v] in enumerate(f.values ?? []) => <td key={j} class={rec(plans[j] ?? {}) ? "ui-pricing-rec" : null}> {v == true ? <span.ui-pricing-yes><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg><span.ui-sr>Included</span></span> : v == false || v == null ? <span.ui-pricing-no aria-hidden="true">—</span> : str(v)} {(v == false || v == null) && <span.ui-sr>Not included</span>} </td>} </tr>} </tbody> </table> </div>}Related
PriceTagA bold price with its period (/month) and a note under it, for plans, rooms and seats.6 views · 4 downloadsPricingCardOne plan: name, price and period, what it includes, and a button; highlighted with a badge when it is the one to pick.7 views · 6 downloadsCartItemOne line of a cart: picture, name and options, a quantity control, the line's total and Remove, as inputs a form sends.6 views · 6 downloadsCartSummarySubtotal, shipping, tax, a discount and the total added up, with a Checkout button as a link, a form or a callback.6 views · 7 downloadsDiscountBadge"−20%" or "Save $10" as a small label, in tones, solid, soft or outline, or pinned to an image's corner.7 views · 8 downloadsPlanToggleMonthly or yearly billing as a pill of two radios, with the saving shown on the yearly side.6 views · 4 downloads
Thread
Thread 0 posts
On PricingTable: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.