Not affiliated with official shadcn/ui. Visit ui.shadcn.com for official docs.
Unlock this block—Get Pro at 60% offReact Dialog Block Sort Options
Sort options dialog with field selection, order direction, and multi-level sorting
Looking to implement shadcn/ui blocks?
Join our Discord community for help from other developers.
Organize data with flexible sorting controls. This React sort options dialog provides sortable field selection from available columns, ascending or descending direction toggle, multi-level sorting with add/remove capability, and drag to reorder sort priority. Built with shadcn/ui Dialog, Select, Button, RadioGroup, and Badge components using Tailwind CSS, users control data organization precisely. Pick fields, set direction, arrange priority—perfect for data tables, file managers, list views, or any Next.js application requiring customizable sorting with multiple criteria support.
"use client";import { useState } from "react";import { ArrowUpDown, ArrowUp, ArrowDown, Plus, X, Check, Loader2, GripVertical, RotateCcw,} from "lucide-react";import { Button } from "@/components/ui/button";import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/components/ui/dialog";import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/ui/select";import { Label } from "@/components/ui/label";import { Badge } from "@/components/ui/badge";import { Separator } from "@/components/ui/separator";import { cn } from "@/lib/utils";type SortField = { id: string; label: string; type: "string" | "number" | "date";};type SortCriteria = { field: string; direction: "asc" | "desc";};const availableFields: SortField[] = [ { id: "name", label: "Name", type: "string" }, { id: "date_created", label: "Date Created", type: "date" }, { id: "date_modified", label: "Date Modified", type: "date" }, { id: "size", label: "Size", type: "number" }, { id: "type", label: "Type", type: "string" }, { id: "status", label: "Status", type: "string" }, { id: "priority", label: "Priority", type: "number" }, { id: "assignee", label: "Assignee", type: "string" },];const defaultSort: SortCriteria[] = [ { field: "date_modified", direction: "desc" },];export const title = "React Dialog Block Sort Options";export default function DialogSortOptions() { const [open, setOpen] = useState(false); const [sortCriteria, setSortCriteria] = useState<SortCriteria[]>(defaultSort); const [isApplying, setIsApplying] = useState(false); const usedFields = sortCriteria.map((c) => c.field); const availableForAdd = availableFields.filter( (f) => !usedFields.includes(f.id) ); const handleAddSort = () => { if (availableForAdd.length === 0) return; setSortCriteria([ ...sortCriteria, { field: availableForAdd[0].id, direction: "asc" }, ]); }; const handleRemoveSort = (index: number) => { setSortCriteria(sortCriteria.filter((_, i) => i !== index)); }; const handleFieldChange = (index: number, field: string) => { const updated = [...sortCriteria]; updated[index].field = field; setSortCriteria(updated); }; const handleDirectionChange = (index: number) => { const updated = [...sortCriteria]; updated[index].direction = updated[index].direction === "asc" ? "desc" : "asc"; setSortCriteria(updated); }; const handleMoveUp = (index: number) => { if (index === 0) return; const updated = [...sortCriteria]; [updated[index - 1], updated[index]] = [updated[index], updated[index - 1]]; setSortCriteria(updated); }; const handleMoveDown = (index: number) => { if (index === sortCriteria.length - 1) return; const updated = [...sortCriteria]; [updated[index], updated[index + 1]] = [updated[index + 1], updated[index]]; setSortCriteria(updated); }; const handleReset = () => { setSortCriteria(defaultSort); }; const handleApply = async () => { setIsApplying(true); await new Promise((resolve) => setTimeout(resolve, 800)); setIsApplying(false); setOpen(false); }; const getFieldLabel = (fieldId: string) => { return availableFields.find((f) => f.id === fieldId)?.label || fieldId; }; return ( <Dialog open={open} onOpenChange={setOpen}> <div className="flex min-h-[350px] items-center justify-center"> <DialogTrigger asChild> <Button variant="outline"> <ArrowUpDown className="mr-2 h-4 w-4" /> Sort Options </Button> </DialogTrigger> </div> <DialogContent className="sm:max-w-md"> <DialogHeader> <DialogTitle className="flex items-center gap-2"> <ArrowUpDown className="h-5 w-5" /> Sort Options </DialogTitle> <DialogDescription> Configure how items are sorted. </DialogDescription> </DialogHeader> <div className="space-y-4 py-4"> {/* Sort Criteria List */} <div className="space-y-2"> {sortCriteria.map((criteria, index) => { const field = availableFields.find( (f) => f.id === criteria.field ); const otherUsedFields = sortCriteria .filter((_, i) => i !== index) .map((c) => c.field); const selectableFields = availableFields.filter( (f) => !otherUsedFields.includes(f.id) ); return ( <div key={index} className="flex items-center gap-2 rounded-lg border p-3" > {/* Priority Indicator */} <div className="flex flex-col items-center gap-1"> <Button variant="ghost" size="icon" className="h-5 w-5" onClick={() => handleMoveUp(index)} disabled={index === 0} > <ArrowUp className="h-3 w-3" /> </Button> <Badge variant="outline" className="text-xs px-1.5"> {index + 1} </Badge> <Button variant="ghost" size="icon" className="h-5 w-5" onClick={() => handleMoveDown(index)} disabled={index === sortCriteria.length - 1} > <ArrowDown className="h-3 w-3" /> </Button> </div> {/* Field Select */} <div className="flex-1"> <Select value={criteria.field} onValueChange={(value) => handleFieldChange(index, value)} > <SelectTrigger className="h-9"> <SelectValue /> </SelectTrigger> <SelectContent> {selectableFields.map((f) => ( <SelectItem key={f.id} value={f.id}> {f.label} </SelectItem> ))} </SelectContent> </Select> </div> {/* Direction Toggle */} <Button variant="outline" size="sm" className="gap-1.5 shrink-0" onClick={() => handleDirectionChange(index)} > {criteria.direction === "asc" ? ( <> <ArrowUp className="h-3.5 w-3.5" /> <span className="text-xs">A→Z</span> </> ) : ( <> <ArrowDown className="h-3.5 w-3.5" /> <span className="text-xs">Z→A</span> </> )} </Button> {/* Remove Button */} <Button variant="ghost" size="icon" className="h-8 w-8 shrink-0" onClick={() => handleRemoveSort(index)} disabled={sortCriteria.length === 1} > <X className="h-4 w-4" /> </Button> </div> ); })} </div> {/* Add Sort Level */} {availableForAdd.length > 0 && sortCriteria.length < 4 && ( <Button variant="outline" size="sm" className="w-full" onClick={handleAddSort} > <Plus className="mr-2 h-4 w-4" /> Add Sort Level </Button> )} <Separator /> {/* Summary */} <div className="space-y-2"> <Label className="text-sm font-medium">Sort Order</Label> <div className="rounded-lg border p-3"> <p className="text-sm text-muted-foreground"> {sortCriteria.map((c, i) => ( <span key={i}> {i > 0 && ", then by "} <span className="font-medium text-foreground"> {getFieldLabel(c.field)} </span> {" "} ({c.direction === "asc" ? "ascending" : "descending"}) </span> ))} </p> </div> </div> </div> <DialogFooter className="flex-col sm:flex-row gap-2"> <Button variant="ghost" size="sm" onClick={handleReset} disabled={isApplying} className="mr-auto" > <RotateCcw className="mr-2 h-4 w-4" /> Reset </Button> <Button variant="outline" onClick={() => setOpen(false)} disabled={isApplying} > Cancel </Button> <Button onClick={handleApply} disabled={sortCriteria.length === 0 || isApplying} > {isApplying ? ( <> <Loader2 className="mr-2 h-4 w-4 animate-spin" /> Applying... </> ) : ( <> <Check className="mr-2 h-4 w-4" /> Apply Sort </> )} </Button> </DialogFooter> </DialogContent> </Dialog> );}Installation
npx shadcn@latest add https://www.shadcn.io/registry/dialog-sort-options.jsonnpx shadcn@latest add https://www.shadcn.io/registry/dialog-sort-options.jsonpnpm dlx shadcn@latest add https://www.shadcn.io/registry/dialog-sort-options.jsonbunx shadcn@latest add https://www.shadcn.io/registry/dialog-sort-options.jsonRelated blocks you will also like
React Dialog Block Filter Sort
Combined filtering
React Dialog Block Reorder Items
Manual ordering
React Dialog Block Select Template
List organization
React Dialog Block Bulk Actions
Batch operations
React Dialog Block Export Data
Data export
React Dialog Block Data Import
Data management