GitHub

File tree

  • docs

    • app/components

      • chat

      • content

    • content/docs/1.getting-started/5.theme

  • src

    • runtime

      • components

        • prose

    • theme

      • content

      • prose

  • test/components

    • __snapshots__

    • content/__snapshots__

Original file line numberDiff line numberDiff line change

@@ -346,11 +346,9 @@ defineShortcuts({

346346

@submit="onSubmit"

347347

>

348348

<template #footer>

349-

<div class="flex items-center gap-1.5 text-xs text-dimmed">

350-

<NuxtLink to="https://vercel.com/ai-gateway" target="_blank" class="inline-flex items-center gap-1 hover:text-muted transition-colors">

351-

Powered by <UIcon name="i-simple-icons-vercel" class="size-3" /> AI Gateway

352-

</NuxtLink>

353-

</div>

349+

<ULink to="https://vercel.com/ai-gateway" target="_blank" class="inline-flex items-center gap-1 text-xs text-dimmed hover:text-muted">

350+

Powered by <UIcon name="i-simple-icons-vercel" class="size-3" /> AI Gateway

351+

</ULink>

354352
355353

<UChatPromptSubmit

356354

size="sm"

Original file line numberDiff line numberDiff line change

@@ -115,9 +115,9 @@ function normalizeCommitMessage(commit: Commit) {

115115

>

116116

<template #title="{ item }">

117117

<UBadge v-if="item.tag === 'unreleased'" color="neutral" variant="subtle" :label="item.title" class="w-12.5 justify-center" />

118-

<NuxtLink v-else :to="item.url" target="_blank" class="hover:underline">

118+

<ULink v-else :to="item.url" target="_blank">

119119

<UBadge variant="subtle" :label="item.tag" />

120-

</NuxtLink>

120+

</ULink>

121121
122122

<time v-if="item.published_at" :datetime="item.published_at" class="text-xs text-dimmed font-normal">

123123

{{ useTimeAgo(new Date(item.published_at)) }}

Original file line numberDiff line numberDiff line change

@@ -34,7 +34,7 @@ const items = [

3434
3535

<template>

3636

<UContextMenu :items="items">

37-

<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-highlighted min-w-0 focus-visible:outline-primary shrink-0" aria-label="Nuxt UI">

37+

<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-highlighted min-w-0 outline-primary/25 focus-visible:outline-3 shrink-0 rounded-md p-1 -ms-1" aria-label="Nuxt UI">

3838

<Logo ref="logoRef" class="w-auto h-6 shrink-0" />

3939

</NuxtLink>

4040

</UContextMenu>

Original file line numberDiff line numberDiff line change

@@ -288,6 +288,54 @@ You can customize these CSS variables in your `main.css` file:

288288

```

289289

::

290290
291+

## Focus :badge{label="Soon" class="align-text-top"}

292+
293+

Nuxt UI applies a `focus-visible` outline on every interactive element, tinted with the `color` prop of the component, e.g. `outline-primary/25` when `color="primary"` or `outline-inverted/25` when `color="neutral"`.

294+
295+

If you prefer a single outline color across your entire app regardless of the component color, you can add a global rule in your `main.css` file:

296+
297+

::code-group

298+
299+

```css [Primary]

300+

@import "tailwindcss";

301+

@import "@nuxt/ui";

302+
303+

*,

304+

::before,

305+

::after {

306+

@apply outline-primary/25;

307+

}

308+
309+

*:focus-visible,

310+

*:has(> a:focus-visible) {

311+

--tw-ring-color: var(--ui-primary);

312+

}

313+

```

314+
315+

```css [Neutral]

316+

@import "tailwindcss";

317+

@import "@nuxt/ui";

318+
319+

*,

320+

::before,

321+

::after {

322+

@apply outline-inverted/25;

323+

}

324+
325+

*:focus-visible,

326+

*:has(> a:focus-visible) {

327+

--tw-ring-color: var(--ui-border-inverted);

328+

}

329+

```

330+
331+

::

332+
333+

The first rule tints the focus outline of every component, the second one aligns the ring recolor applied on focus by variants with a visible border like `outline` or `subtle`, including cards that highlight when their link is focused.

334+
335+

::note

336+

Make sure to declare these rules outside of any `@layer` so they take precedence over the colors set by the component themes. Keep in mind the first rule also tints the native focus outline of your own elements, and variants recoloring a `border` on focus like [FileUpload](/docs/components/file-upload) or [ContentSurround](/docs/components/content-surround) keep following the component `color`.

337+

::

338+
291339

## Radius

292340
293341

Nuxt UI overrides Tailwind CSS's default `rounded-*` utilities with a unified border radius system, allowing you to use regular [border radius utilities](https://tailwindcss.com/docs/border-radius) like `rounded-xs` or `rounded-2xl`:

Original file line numberDiff line numberDiff line change

@@ -162,7 +162,6 @@ function onClose() {

162162

:aria-label="props.title"

163163

v-bind="{ to: props.to, target: props.target, ...$attrs }"

164164

:class="prefix('focus:outline-none')"

165-

tabindex="-1"

166165

raw

167166

>

168167

<span :class="prefix('absolute inset-0')" aria-hidden="true" />

Original file line numberDiff line numberDiff line change

@@ -126,6 +126,14 @@ const ariaLabel = computed(() => {

126126
127127

<template>

128128

<Primitive :as="props.as" :data-orientation="props.orientation" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })" @click="props.onClick">

129+

<ULink

130+

v-if="props.to"

131+

:aria-label="ariaLabel"

132+

v-bind="{ to: props.to, target: props.target, ...$attrs }"

133+

:class="prefix('focus:outline-none absolute inset-0')"

134+

raw

135+

/>

136+
129137

<div v-if="props.image || !!slots.header" data-slot="header" :class="ui.header({ class: props.ui?.header })">

130138

<slot name="header" :ui="ui">

131139

<component

@@ -138,16 +146,6 @@ const ariaLabel = computed(() => {

138146

</div>

139147
140148

<div data-slot="body" :class="ui.body({ class: props.ui?.body })">

141-

<ULink

142-

v-if="props.to"

143-

:aria-label="ariaLabel"

144-

v-bind="{ to: props.to, target: props.target, ...$attrs }"

145-

:class="prefix('focus:outline-none peer')"

146-

raw

147-

>

148-

<span :class="prefix('absolute inset-0')" aria-hidden="true" />

149-

</ULink>

150-
151149

<slot name="body">

152150

<div v-if="(date || !!slots.date) || (props.badge || !!slots.badge)" data-slot="meta" :class="ui.meta({ class: props.ui?.meta })">

153151

<slot name="badge">

Original file line numberDiff line numberDiff line change

@@ -71,7 +71,6 @@ function copyCode() {

7171

size="sm"

7272

:aria-label="t('prose.pre.copy')"

7373

:class="ui.copy({ class: props.ui?.copy })"

74-

tabindex="-1"

7574

@click="copyCode"

7675

/>

7776
Original file line numberDiff line numberDiff line change

@@ -5,6 +5,13 @@

55

@variant light (&:where(.light, .light *));

66

@variant dark (&:where(.dark, .dark *));

77
8+

@layer base {

9+

/* Chromium applies an `outline-offset` on focused links, reset it so focus outlines hug elements consistently */

10+

a:focus-visible {

11+

outline-offset: 0;

12+

}

13+

}

14+
815

@layer theme {

916

:root, :host {

1017

--ui-header-height: 4rem;

Original file line numberDiff line numberDiff line change

@@ -1,40 +1,48 @@

11

@keyframes accordion-up {

22

from {

33

height: var(--reka-accordion-content-height);

4+

overflow: hidden;

45

}

56
67

to {

78

height: 0;

9+

overflow: hidden;

810

}

911

}

1012
1113

@keyframes accordion-down {

1214

from {

1315

height: 0;

16+

overflow: hidden;

1417

}

1518
1619

to {

1720

height: var(--reka-accordion-content-height);

21+

overflow: hidden;

1822

}

1923

}

2024
2125

@keyframes collapsible-up {

2226

from {

2327

height: var(--reka-collapsible-content-height);

28+

overflow: hidden;

2429

}

2530
2631

to {

2732

height: 0;

33+

overflow: hidden;

2834

}

2935

}

3036
3137

@keyframes collapsible-down {

3238

from {

3339

height: 0;

40+

overflow: hidden;

3441

}

3542
3643

to {

3744

height: var(--reka-collapsible-content-height);

45+

overflow: hidden;

3846

}

3947

}

4048
Original file line numberDiff line numberDiff line change

@@ -3,8 +3,8 @@ export default {

33

root: 'w-full',

44

item: 'border-b border-default last:border-b-0',

55

header: 'flex',

6-

trigger: 'group flex-1 flex items-center gap-1.5 font-medium text-sm py-3.5 focus-visible:outline-primary min-w-0',

7-

content: 'data-[state=open]:animate-[accordion-down_200ms_ease-out] data-[state=closed]:animate-[accordion-up_200ms_ease-out] overflow-hidden focus:outline-none',

6+

trigger: 'group flex-1 flex items-center gap-1.5 font-medium text-sm py-3.5 outline-primary/25 focus-visible:outline-3 min-w-0 rounded-md',

7+

content: 'data-[state=open]:animate-[accordion-down_200ms_ease-out] data-[state=closed]:animate-[accordion-up_200ms_ease-out] data-[state=closed]:overflow-hidden focus:outline-none',

88

body: 'text-sm pb-3.5',

99

leadingIcon: 'shrink-0 size-5',

1010

trailingIcon: 'shrink-0 size-5 ms-auto group-data-[state=open]:rotate-180 transition-transform duration-200',

Read the original on github.com ↗