Add .btn-outline-gradient buttons and use one for the All Package navbar link

- `$gradients` map shared by `.card-gradient-*` and `.btn-outline-gradient-*`
- All Package link also renders on the local dev server
This commit is contained in:
codecalm committed 2026-09-25 00:34:24 +02:00
1 parent 9cebb7a185
commit 0eea8b7e2b
11 files changed
+216 -39

No files matched your search

+1
View File
@@ -30,6 +30,7 @@ const TOKENS = [
{ name: 'aspectRatios', type: 'AspectRatio', module: 'v', variable: '$aspect-ratios', kind: 'map', source: 'core/scss/_variables.scss' },
{ name: 'breakpoints', type: 'Breakpoint', module: 's', variable: '$grid-breakpoints', kind: 'map', source: 'core/scss/_settings.scss' },
{ name: 'breadcrumbVariants', type: 'BreadcrumbVariant', module: 'v', variable: '$breadcrumb-variants', kind: 'map', source: 'core/scss/_variables.scss' },
{ name: 'gradients', type: 'Gradient', module: 'v', variable: '$gradients', kind: 'map', source: 'core/scss/_variables.scss' },
{ name: 'formValidationStates', type: 'FormValidationState', module: 'v', variable: '$form-validation-states', kind: 'map', source: 'core/scss/_variables.scss' },
{ name: 'paymentProviders', type: 'PaymentProvider', module: 'v', variable: '$payment-providers', kind: 'list', source: 'core/scss/_variables.scss' },
{ name: 'flagCountries', type: 'FlagCountry', module: 'v', variable: '$flag-countries', kind: 'list', source: 'core/scss/_variables.scss' },
+7
View File
@@ -0,0 +1,7 @@
---
"@tabler/core": minor
"@tabler/preview": minor
"@tabler/docs": minor
---
Added `.btn-outline-gradient` buttons with a gradient border, using the named gradients shared with `.card-gradient`.
+57
View File
@@ -1207,6 +1207,63 @@ $otp-slot-box-shadow: var(--shadow-input) !default;
$otp-separator-color: var(--secondary) !default;
// scss-docs-end otp-variables
// Gradients
// Multi-color presets shared by `.card-gradient-*` and `.btn-outline-gradient-*`.
// Each value is a comma-separated list of color stops.
// scss-docs-start gradients
$gradients: (
'rainbow': (
#78c5d6,
#459ba8,
#79c267,
#c5d647,
#f5d63d,
#f08b33,
#e868a2,
#be61a5,
),
'sun': (
#fd1d1d,
#fcb045,
),
'snow': (
#333,
#e9ecef,
),
'ocean': (
#1cb5e0,
#000851,
),
'mellow': (
#f8ff00,
#3ad59f,
),
'disco': (
#fc466b,
#3f5efb,
),
'psychedelic': (
#fcc5e4,
#fda34b,
#ff7882,
#c8699e,
#7046aa,
#0c1db8,
#020f75,
),
'love': (
#f235e6,
#bc0707,
),
'gold': (
#9d4100,
#bf7122,
#f59f00,
#ffd700,
),
) !default;
// scss-docs-end gradients
// Cards
// scss-docs-start card-variables
$card-spacer-y: 1.25rem !default;
+23
View File
@@ -0,0 +1,23 @@
// `.btn-outline-gradient-*` presets share one selector list with the base
// class, so a preset works without `.btn-outline-gradient` next to it. The
// stops come from the shared `$gradients` map, so a card and a button with the
// same name show the same gradient.
@use 'sass:list';
@use 'sass:map';
@use '../config' as *;
@use '../ui/buttons' as buttons;
@include describe('btn-outline-gradient') {
@include it('shares the named gradient stops with .card-gradient') {
@include assert-equal(map.get($gradients, 'sun'), (#fd1d1d, #fcb045));
@include assert-true(map.has-key($gradients, 'rainbow'));
}
@include it('lists the base class and every color and gradient preset') {
$selectors: buttons.btn-gradient-selectors();
@include assert-equal(list.nth($selectors, 1), '.btn-outline-gradient');
@include assert-true(list.index($selectors, '.btn-outline-gradient-primary') != null);
@include assert-true(list.index($selectors, '.btn-outline-gradient-rainbow') != null);
@include assert-equal(list.length($selectors), 1 + list.length(map.keys($theme-colors)) + list.length(map.keys($gradients)));
}
}
+60
View File
@@ -175,6 +175,66 @@ $btn-colors: map.keys(map.merge($theme-colors, $social-colors));
--btn-box-shadow: none;
}
// Gradient ring: a surface-colored layer clipped to the padding box sits on a
// gradient layer that fills the border box, so only the border shows the
// gradient — the same trick as `.card-gradient`. Presets share the `.btn-outline-gradient`
// base through one selector list, so a preset class works on its own.
$btn-gradient-names: list.join(map.keys($theme-colors), map.keys($gradients));
@function btn-gradient-selectors() {
$selectors: '.btn-outline-gradient';
@each $name in $btn-gradient-names {
$selectors: list.append($selectors, '.btn-outline-gradient-#{$name}', $separator: comma);
}
@return $selectors;
}
$btn-gradient-selectors: btn-gradient-selectors();
#{$btn-gradient-selectors} {
// scss-docs-start btn-gradient-css-vars
--btn-gradient: var(--primary), var(--primary);
--btn-gradient-direction: 135deg;
// scss-docs-end btn-gradient-css-vars
--btn-border-color: transparent;
--btn-hover-border-color: transparent;
--btn-active-border-color: transparent;
--btn-disabled-border-color: transparent;
--btn-box-shadow: none;
// The hover and active fills are translucent tints, so a solid surface layer
// sits under them — otherwise the gradient would bleed through on hover.
background:
linear-gradient(var(--btn-bg), var(--btn-bg)) padding-box,
linear-gradient(var(--bg-surface), var(--bg-surface)) padding-box,
linear-gradient(var(--btn-gradient-direction), var(--btn-gradient)) border-box;
}
// `.btn:hover` only sets `background-color`, which sits under the two opaque
// image layers, so the hover and active fills go through the token instead.
// `:is()` keeps the state rules to one selector list each.
:is(#{$btn-gradient-selectors}):hover,
.btn-check + :is(#{$btn-gradient-selectors}):hover {
--btn-bg: var(--btn-hover-bg);
}
:is(#{$btn-gradient-selectors}):active,
:is(#{$btn-gradient-selectors}).active,
.btn-check:checked + :is(#{$btn-gradient-selectors}) {
--btn-bg: var(--btn-active-bg);
}
@each $color in map.keys($theme-colors) {
.btn-outline-gradient-#{$color} {
--btn-gradient: var(--#{$color}), var(--#{$color}-darken);
}
}
@each $name, $stops in $gradients {
.btn-outline-gradient-#{$name} {
--btn-gradient: #{$stops};
}
}
//
// Button sizes
//
+4 -34
View File
@@ -734,40 +734,10 @@ Card gradient
}
}
.card-gradient-rainbow {
--card-gradient: #78c5d6, #459ba8, #79c267, #c5d647, #f5d63d, #f08b33, #e868a2, #be61a5;
}
.card-gradient-sun {
--card-gradient: #fd1d1d, #fcb045;
}
.card-gradient-snow {
--card-gradient: #333, #e9ecef;
}
.card-gradient-ocean {
--card-gradient: #1cb5e0, #000851;
}
.card-gradient-mellow {
--card-gradient: #f8ff00, #3ad59f;
}
.card-gradient-disco {
--card-gradient: #fc466b, #3f5efb;
}
.card-gradient-psychedelic {
--card-gradient: #fcc5e4, #fda34b, #ff7882, #c8699e, #7046aa, #0c1db8, #020f75;
}
.card-gradient-love {
--card-gradient: #f235e6, #bc0707;
}
.card-gradient-gold {
--card-gradient: #9d4100, #bf7122, #f59f00, #ffd700;
@each $name, $stops in $gradients {
.card-gradient-#{$name} {
--card-gradient: #{$stops};
}
}
.card-gradient-animated {
+26
View File
@@ -25,6 +25,8 @@ classnames:
style:
- class: btn-outline
desc: Transparent fill with a colored border
- class: btn-outline-gradient
desc: Gradient ring on the border; btn-outline-gradient-{color} and the named gradients change the colors
- class: btn-ghost
desc: No fill and no border until hovered
- class: btn-link
@@ -187,6 +189,30 @@ The `.btn-outline-*` classes keep the color on the border and the text and leave
</div>
</Example>
## Outline gradient buttons
`.btn-outline-gradient` keeps the fill empty, like an outline button, but draws the border as a gradient. Add a theme color, such as `.btn-outline-gradient-purple`, or one of the named gradients shared with [card gradients](/ui/components/card-gradient). The fill still follows the theme, so the ring works in dark mode without extra classes.
<Example centered>
<div class="btn-list">
<a href="#" class="btn btn-outline-gradient">Default</a>
<a href="#" class="btn btn-outline-gradient-purple">Purple</a>
<a href="#" class="btn btn-outline-gradient-rainbow">Rainbow</a>
<a href="#" class="btn btn-outline-gradient-sun">Sun</a>
<a href="#" class="btn btn-outline-gradient-ocean">Ocean</a>
<a href="#" class="btn btn-outline-gradient-disco">Disco</a>
</div>
</Example>
Set `--tblr-btn-gradient` on the button to use your own color stops, and `--tblr-btn-gradient-direction` to change the angle.
<Example centered>
<div class="btn-list">
<a href="#" class="btn btn-outline-gradient" style="--tblr-btn-gradient: #066fd1, #ae3ec9, #f76707">Custom stops</a>
<a href="#" class="btn btn-outline-gradient-rainbow" style="--tblr-btn-gradient-direction: 90deg">Left to right</a>
</div>
</Example>
## Button sizes
Add `.btn-sm`, `.btn-lg` or `.btn-xl` to change the size. A larger button draws the eye to the main action, and a smaller one fits into toolbars and table rows.
+1 -1
View File
@@ -29,7 +29,7 @@ classnames:
---
import Example from '@components/Example.astro'
export const gradients = ['rainbow', 'sun', 'snow', 'ocean', 'mellow', 'disco', 'psychedelic', 'love', 'gold']
import { gradients } from '@shared/lib/tokens'
## Default markup
+28
View File
@@ -9,6 +9,7 @@ import CardBody from '@ui/CardBody.astro'
import CardTitle from '@ui/CardTitle.astro'
import CardSubtitle from '@ui/CardSubtitle.astro'
import site from '@data/site.json'
import { gradients } from '@shared/lib/tokens'
import DocsLink from '@ui/DocsLink.astro'
type ColorEntry = [string, { title: string; icon?: string }]
@@ -170,6 +171,33 @@ const sizes = ['sm', 'md', 'lg', 'xl'] as const
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Outline gradient</CardTitle>
<CardSubtitle>Only the border carries the gradient. Use a theme color or one of the named gradients shared with <code>.card-gradient</code>.</CardSubtitle>
<ButtonList>
<button type="button" class="btn btn-outline-gradient">Default</button>
{
themeColors.slice(0, 6).map(([name, color]) => (
<button type="button" class={`btn btn-outline-gradient-${name}`}>
{color.icon && <Icon name={color.icon} />} {color.title}
</button>
))
}
</ButtonList>
<ButtonList class="mt-3">
{
gradients.map((name) => (
<button type="button" class={`btn btn-outline-gradient-${name}`}>
{ucFirst(name)}
</button>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
+5 -4
View File
@@ -40,18 +40,19 @@ const {
} = Astro.props
// The All Package link is an ad for tabler.io, so it only renders on the hosted demo
// (Vercel deploys), never in local builds or the packaged template (VERCEL_ENV is unset there).
const isVercelDeploy = !!process.env.VERCEL_ENV
// (Vercel deploys) and on the local dev server, never in a local build or the
// packaged template (VERCEL_ENV is unset and DEV is false there).
const showAllPackage = !!process.env.VERCEL_ENV || import.meta.env.DEV
const allPackageUrl = 'https://tabler.io/pricing?ref=tabler-preview-navbar#bundle'
---
<!-- BEGIN NAVBAR ACTIONS -->
<div class:list={['navbar-nav flex-row', className]}>
{
!condensed && isVercelDeploy && (
!condensed && showAllPackage && (
<div class={`nav-item d-none d-${breakpoint}-flex me-3`}>
<ButtonList>
<Button icon="package" iconColor="blue" text="All Package" href={allPackageUrl} external />
<Button icon="package" iconColor="purple" variant="outline" color="gradient-rainbow" text="All Package" href={allPackageUrl} external />
</ButtonList>
</div>
)
+4
View File
@@ -33,6 +33,10 @@ export type Breakpoint = (typeof breakpoints)[number]
export const breadcrumbVariants = ['dots', 'arrows', 'bullets'] as const
export type BreadcrumbVariant = (typeof breadcrumbVariants)[number]
/** Keys of `$gradients` (core/scss/_variables.scss). */
export const gradients = ['rainbow', 'sun', 'snow', 'ocean', 'mellow', 'disco', 'psychedelic', 'love', 'gold'] as const
export type Gradient = (typeof gradients)[number]
/** Keys of `$form-validation-states` (core/scss/_variables.scss). */
export const formValidationStates = ['valid', 'invalid'] as const
export type FormValidationState = (typeof formValidationStates)[number]