Highlight
highlight ui Highlights matching substrings in text for search results. Supports string or regex queries, case-sensitive and whole-word matching, custom highlight tag, and a max-highlight cap.
Also available for React ->Installation
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/highlight.json $ npx shadcn-vue@latest add https://uipkge.dev/r/vue/highlight.json $ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/highlight.json $ bunx shadcn-vue@latest add https://uipkge.dev/r/vue/highlight.json Named registry:
npx shadcn-vue@latest add @uipkge/highlight Installs to: app/components/ui/highlight/ Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
text Text to search within. | string | — | required |
query Query string or RegExp to highlight. | string | RegExp | — | required |
highlightTag HTML tag used to wrap matched substrings. Default 'mark'. | 'mark''span' | 'mark' | optional |
highlightClass Class applied to each highlight wrapper. | HTMLAttributes['class'] | — | optional |
highlightStyle Inline style applied to each highlight wrapper. | HTMLAttributes['style'] | — | optional |
caseSensitive Case-sensitive matching. Default false. | boolean | false | optional |
wholeWord Match whole words only. Default false. | boolean | false | optional |
maxHighlights Cap the number of highlights rendered. 0 = unlimited. Default 0. | number | 0 | optional |
class | HTMLAttributes['class'] | — | optional |
Schema
Type aliases from this item's source — use them to shape the data you pass in.
Segment interface Segment {
text: string
match: boolean
} Files installed (2)
-
app/components/ui/highlight/Highlight.vue 3.7 kB
<script setup lang="ts"> import { computed, onMounted, watch, type HTMLAttributes } from 'vue' import { cn } from '@/lib/utils' interface Props { /** Text to search within. */ text: string /** Query string or RegExp to highlight. */ query: string | RegExp /** HTML tag used to wrap matched substrings. Default 'mark'. */ highlightTag?: 'mark' | 'span' /** Class applied to each highlight wrapper. */ highlightClass?: HTMLAttributes['class'] /** Inline style applied to each highlight wrapper. */ highlightStyle?: HTMLAttributes['style'] /** Case-sensitive matching. Default false. */ caseSensitive?: boolean /** Match whole words only. Default false. */ wholeWord?: boolean /** Cap the number of highlights rendered. 0 = unlimited. Default 0. */ maxHighlights?: number class?: HTMLAttributes['class'] } const props = withDefaults(defineProps<Props>(), { highlightTag: 'mark', caseSensitive: false, wholeWord: false, maxHighlights: 0, }) const emit = defineEmits<{ /** Emitted when the query changes. Provides the total match count (before maxHighlights cap). */ matchCount: [count: number] }>() interface Segment { text: string match: boolean } const segments = computed<Segment[]>(() => { const text = props.text const query = props.query if (!text) return [] if (!query) return [{ text, match: false }] let pattern: RegExp if (query instanceof RegExp) { const flags = query.flags.includes('g') ? query.flags : query.flags + 'g' pattern = new RegExp(query.source, flags) } else { if (!query) return [{ text, match: false }] const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') const body = props.wholeWord ? `\\b${escaped}\\b` : escaped const flags = props.caseSensitive ? 'g' : 'gi' pattern = new RegExp(body, flags) } const out: Segment[] = [] let last = 0 let count = 0 let m: RegExpExecArray | null while ((m = pattern.exec(text)) !== null) { if (m.index > last) out.push({ text: text.slice(last, m.index), match: false }) out.push({ text: m[0], match: true }) last = m.index + m[0].length count++ if (props.maxHighlights > 0 && count >= props.maxHighlights) break if (m[0] === '') pattern.lastIndex++ } if (last < text.length) out.push({ text: text.slice(last), match: false }) return out }) // Total match count (uncapped) — emitted via watch to avoid side-effects in computed const totalMatchCount = computed(() => { const text = props.text const query = props.query if (!text || !query) return 0 let pattern: RegExp if (query instanceof RegExp) { const flags = query.flags.includes('g') ? query.flags : query.flags + 'g' pattern = new RegExp(query.source, flags) } else { const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') const body = props.wholeWord ? `\\b${escaped}\\b` : escaped const flags = props.caseSensitive ? 'g' : 'gi' pattern = new RegExp(body, flags) } let total = 0 let m: RegExpExecArray | null while ((m = pattern.exec(text)) !== null) { total++ if (m[0] === '') pattern.lastIndex++ } return total }) watch(totalMatchCount, (n) => emit('matchCount', n)) onMounted(() => emit('matchCount', totalMatchCount.value)) </script> <template> <span data-uipkge data-slot="highlight" :class="cn(props.class)"> <template v-for="(seg, i) in segments" :key="i"> <component :is="highlightTag" v-if="seg.match" data-slot="highlight-match" :class="cn('bg-yellow-200 text-yellow-950 dark:bg-yellow-500/30 dark:text-yellow-100 rounded px-0.5 font-medium', highlightClass)" :style="highlightStyle" >{{ seg.text }}</component > <template v-else>{{ seg.text }}</template> </template> </span> </template> -
app/components/ui/highlight/index.ts 0.1 kB
export { default as Highlight } from './Highlight.vue'
Raw manifest: https://uipkge.dev/r/vue/highlight.json