בחירה
תפריט נפתח לבחירת אפשרות אחת — שדות מדינה, סדר מיון, הגדרות ערכת נושא — עם מסגרות עבות בסגנון ניאו-ברוטליסטי וצללים חדים.
import {
Select,
SelectContent,ה-select פותח רשימת אפשרויות קופצת ומציג את הנבחרת בטריגר שלו — תחליף מעוצב ומוכן לטפסים ל-<select> המקורי. נבנה על גבי רכיב Base UI Select ומעוצב לפי המתכון הנאו-ברוטליסטי: מסגרות עבות, צללים חדים וטיפוגרפיה מודגשת.
השתמשו בו כש:
- שדות טופס עם בחירות קבועות — מדינה, מטבע, אזור זמן; בכל מקום שהייתם משתמשים אחרת ב-
<select>מקורי. - בקרות מיון וסינון — מסדרים רשת מוצרים או טבלה לפי מחיר, תאריך או פופולריות.
- בוחרי העדפות — ערכת נושא, שפה או תדירות התראות בפאנל הגדרות.
התקנה#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/select.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/select.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/select.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/select.json
התקינו את התלויות הבאות:
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
העתיקו והדביקו את הקוד הבא לפרויקט שלכם.
"use client"
import * as React from "react"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { Select as SelectPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return (
<SelectPrimitive.Group
data-slot="select-group"
className={cn("scroll-my-1 p-1", className)}
{...props}
/>
)
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"flex w-fit items-center justify-between gap-1.5 rounded border-2 bg-input py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-sm transition-colors outline-none select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "item-aligned",
align = "center",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
data-align-trigger={position === "item-aligned"}
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded border-2 bg-popover text-popover-foreground shadow-md duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
position={position}
align={align}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
data-position={position}
className={cn(
"data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)",
position === "popper" && ""
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("px-1.5 py-1 text-xs text-muted-foreground", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"relative flex w-full cursor-default items-center gap-1.5 rounded-sm py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
>
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="pointer-events-none" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<ChevronUpIcon />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<ChevronDownIcon />
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}
עדכנו את נתיבי הייבוא כך שיתאימו למבנה הפרויקט שלכם.
שימוש#
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"const items = [
{ label: "Light", value: "light" },
{ label: "Dark", value: "dark" },
{ label: "System", value: "system" },
]
<Select items={items}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>קומפוזיציה#
השתמשו בקומפוזיציה הבאה כדי לבנות Select:
Select
├── SelectTrigger
│ └── SelectValue
└── SelectContent
├── SelectGroup
│ ├── SelectLabel
│ ├── SelectItem
│ └── SelectItem
├── SelectSeparator
└── SelectGroup
├── SelectLabel
├── SelectItem
└── SelectItemדוגמאות#
יישור פריט עם הטריגר#
השתמשו ב-alignItemWithTrigger על SelectContent כדי לקבוע אם הפריט הנבחר מתיישר עם הטריגר. כאשר הערך הוא true (ברירת המחדל), החלונית ממוקמת כך שהפריט הנבחר מופיע מעל הטריגר. כאשר הערך הוא false, החלונית מתיישרת לקצה הטריגר.
"use client"
import * as React from "react"קבוצות#
השתמשו ב-SelectGroup, ב-SelectLabel וב-SelectSeparator כדי לארגן פריטים.
import {
Select,
SelectContent,ניתן לגלילה#
רכיב select עם פריטים רבים שניתן לגלול בו.
import {
Select,
SelectContent,מושבת#
import {
Select,
SelectContent,לא תקין#
הוסיפו את התכונה data-invalid לרכיב Field ואת התכונה aria-invalid לרכיב SelectTrigger כדי להציג מצב שגיאה.
<Field data-invalid>
<FieldLabel>Fruit</FieldLabel>
<SelectTrigger aria-invalid>
<SelectValue />
</SelectTrigger>
</Field>import { Field, FieldError, FieldLabel } from "@/components/ui/field"
import {
Select,RTL#
כדי להפעיל RTL ב-Neobrutalism, עיינו במדריך ההגדרה של RTL.
"use client"
import * as React from "react"נגישות#
ה-select מממש את דפוס Combobox של WAI-ARIA בצורת select-only: הטריגר מוכרז כ-combobox מכווץ עם aria-expanded, ה-popup מרנדר כ-listbox של פריטי option, ו-aria-selected עוקב אחרי הערך הנוכחי — הכול מחווט על ידי הפרימיטיב.
אינטראקציות מקלדת:
| מקש | פעולה |
|---|---|
Space / Enter | פותחים את ה-select; כשפתוח, בוחרים את הפריט המודגש |
ArrowDown / ArrowUp | פותחים כשסגור; כשפתוח, מדגישים את הפריט הבא / הקודם |
Esc | סוגרים את ה-popup ומחזירים מוקד לטריגר |
| מקלידים תווים | Typeahead — קופצים לפריט התואם הראשון |
תיעוד ה-API#
למידע נוסף, עיינו בתיעוד של Base UI.