Input Components
Explore different input types and features—like dropdowns, tags, editable text, and more
import { HvIconContainer, HvInput } from "@hitachivantara/uikit-react-core";
export default function Demo() {
return (
<HvInput
label="Leading Icon"
className="w-300px"
placeholder="Placeholder text..."
startAdornment={
<HvIconContainer>
<div className="i-ph-user" />
</HvIconContainer>
}
/>
);
}import { useRef, useState } from "react";
import { HvAdornment, HvInput } from "@hitachivantara/uikit-react-core";
import { Copy, Success } from "@hitachivantara/uikit-react-icons";
export default function Demo() {
const [copied, setCopied] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const handleCopy = () => {
navigator.clipboard.writeText(inputRef.current?.value || "");
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1000);
};
return (
<HvInput
label="Copy to clipboard"
ref={inputRef}
className="w-300px"
defaultValue="Copy this text"
endAdornment={
<HvAdornment
icon={
<>
<Success
color="positive"
className={`transition-all ${copied ? "scale-100 opacity-100" : "scale-0 opacity-0"}`}
/>
<Copy
className={`absolute transition-all ${copied ? "scale-0 opacity-0" : "scale-100 opacity-100"}`}
/>
</>
}
onClick={handleCopy}
/>
}
/>
);
}Label left side
import {
HvFormElement,
HvInput,
HvLabel,
} from "@hitachivantara/uikit-react-core";
export default function Demo() {
return (
<HvFormElement required className="flex gap-xs">
<HvLabel label="Name" htmlFor="username-input" className="h-fit mt-5px" />
<HvInput
id="username"
className="w-300px"
required
placeholder="My label is on the side..."
/>
</HvFormElement>
);
}import { useState } from "react";
import {
HvInput,
HvOption,
HvOverflowTooltip,
HvSelect,
HvTypography,
} from "@hitachivantara/uikit-react-core";
export default function Demo() {
const [phoneNumber, setPhoneNumber] = useState("");
const [value, setValue] = useState<string | null>(null);
return (
<HvInput
label="Dropdown prefix"
className="w-300px"
onChange={(_, value) => {
setPhoneNumber(value);
}}
placeholder="Type phone number..."
value={phoneNumber}
startAdornment={
<HvSelect
enablePortal
value={value}
onChange={(_, val) => setValue(val)}
classes={{
root: "w-95px! border-r-1! border-r-border! rounded-none!",
panel: "w-200px! max-h-260px!",
select:
"border-none bg-bgContainer hover:bg-primaryDimmed rounded-none!",
}}
placeholder="Select..."
renderValue={(option) => option?.value ?? ""}
>
{countries.map((country) => (
<HvOption
key={country.code}
value={`${country.flag} ${country.code}`}
>
<div className="flex justify-between items-center">
<HvOverflowTooltip data={`${country.flag} ${country.label}`} />
<HvTypography
variant="captionLabel"
className="color-text-subtle"
>
{country.code}
</HvTypography>
</div>
</HvOption>
))}
</HvSelect>
}
/>
);
}
const countries = [
{ flag: "🇦🇺", code: "+61", label: "Australia" },
{ flag: "🇧🇷", code: "+55", label: "Brazil" },
{ flag: "🇨🇦", code: "+1", label: "Canada" },
{ flag: "🇨🇱", code: "+56", label: "Chile" },
{ flag: "🇪🇬", code: "+20", label: "Egypt" },
{ flag: "🇫🇷", code: "+33", label: "France" },
{ flag: "🇩🇪", code: "+49", label: "Germany" },
{ flag: "🇮🇳", code: "+91", label: "India" },
{ flag: "🇮🇹", code: "+39", label: "Italy" },
{ flag: "🇯🇵", code: "+81", label: "Japan" },
{ flag: "🇲🇽", code: "+52", label: "Mexico" },
{ flag: "🇳🇿", code: "+64", label: "New Zealand" },
{ flag: "🇳🇴", code: "+47", label: "Norway" },
{ flag: "🇵🇹", code: "+351", label: "Portugal" },
{ flag: "🇸🇬", code: "+65", label: "Singapore" },
{ flag: "🇿🇦", code: "+27", label: "South Africa" },
{ flag: "🇰🇷", code: "+82", label: "South Korea" },
{ flag: "🇪🇸", code: "+34", label: "Spain" },
{ flag: "🇸🇪", code: "+46", label: "Sweden" },
{ flag: "🇨🇭", code: "+41", label: "Switzerland" },
{ flag: "🇦🇪", code: "+971", label: "United Arab Emirates" },
{ flag: "🇬🇧", code: "+44", label: "United Kingdom" },
{ flag: "🇺🇸", code: "+1", label: "United States" },
];import { useState } from "react";
import { HvInput, HvSelect } from "@hitachivantara/uikit-react-core";
export default function Demo() {
const [email, setEmail] = useState("");
const [role, setRole] = useState<string | null>(null);
return (
<HvInput
label="Dropdown suffix"
className="w-300px"
onChange={(_, value) => {
setEmail(value);
}}
value={email}
placeholder="Type user email..."
endAdornment={
<HvSelect
enablePortal
value={role}
onChange={(_, val) => setRole(val)}
classes={{
root: "w-140px! border-l-1! border-l-border! rounded-none!",
panel: "w-150px! p-sm",
select:
"border-none! rounded-none! bg-bgContainer! hover:bg-primaryDimmed",
popper: "m-l-[-10px]!",
}}
options={roles}
placeholder="Select..."
/>
}
/>
);
}
const roles = [
{ label: "Admin", value: "admin" },
{ label: "Editor", value: "editor" },
{ label: "Reviewer", value: "reviewer" },
{ label: "View only", value: "view" },
];Dropdown with treeview
Select a file or folder...
import { forwardRef, useState } from "react";
import {
HvBaseDropdown,
HvIconContainer,
HvLabel,
HvPanel,
HvTreeItem,
HvTreeItemProps,
HvTreeView,
} from "@hitachivantara/uikit-react-core";
export default function Demo() {
const [file, setFile] = useState("");
const [open, setOpen] = useState(false);
return (
<>
<HvLabel>Dropdown with treeview</HvLabel>
<HvBaseDropdown
expanded={open}
className="w-300px"
placeholder={file || "Select a file or folder..."}
adornment={
<HvIconContainer>
<div className="i-ph-list-bullets" />
</HvIconContainer>
}
classes={{
arrowContainer: "flex w-32px h-full items-center justify-center",
}}
onToggle={(_evt, s) => setOpen(s)}
>
<HvPanel>
<HvTreeView
multiSelect={false}
aria-label="file system navigator"
onNodeSelect={(_, val) => {
console.log(val);
setFile(val);
}}
>
{renderItem(treeDataObject)}
</HvTreeView>
</HvPanel>
</HvBaseDropdown>
</>
);
}
type TreeData = { id: string; label: string; children?: TreeData[] };
const treeDataObject = {
id: "user",
label: "User",
children: [
{
id: "Applications",
label: "Applications",
children: [
{ id: "Code", label: "Code.app" },
{ id: "Chrome", label: "Chrome.app" },
{ id: "Firefox", label: "Firefox.app" },
],
},
{
id: "Documents",
label: "Documents",
children: [{ id: "secret", label: "secret.txt" }],
},
{
id: "git",
label: "git",
children: [
{
id: "uikit-react",
label: "uikit-react",
children: [{ id: "uikit-pkg", label: "package.json" }],
},
{
id: "app-shell",
label: "app-shell",
children: [{ id: "as-pkg", label: "package.json" }],
},
],
},
],
} satisfies TreeData;
const SimpleTreeItem = forwardRef<HTMLLIElement, HvTreeItemProps>(
function SimpleTreeItem(props, ref) {
const { children, nodeId, label, ...others } = props;
const Icon = children
? () => (
<HvIconContainer>
<div className="i-ph-folder" />
</HvIconContainer>
)
: () => (
<HvIconContainer>
<div className="i-ph-file" />
</HvIconContainer>
);
return (
<HvTreeItem
ref={ref}
nodeId={nodeId}
label={
<div className="flex items-center">
<Icon />
<span style={{ flex: 1 }}>{label}</span>
</div>
}
{...others}
>
{children}
</HvTreeItem>
);
},
);
/** Render tree view items */
const renderItem = ({ id, label, children }: TreeData) => (
<SimpleTreeItem key={id} nodeId={id} label={label}>
{children?.map(renderItem)}
</SimpleTreeItem>
);Select colors from the dropdown...
import { useRef, useState } from "react";
import { flushSync } from "react-dom";
import {
HvBaseDropdown,
HvLabel,
HvPanel,
HvTag,
} from "@hitachivantara/uikit-react-core";
const colors = [
"Blue",
"Red",
"Green",
"Yellow",
"Purple",
"White",
"Black",
"Orange",
"Pink",
"Brown",
"Gray",
"Cyan",
"Magenta",
"Lime",
"Teal",
"Lavender",
];
export default function Demo() {
const containerRef = useRef<HTMLDivElement>(null);
const focusTarget = useRef<HTMLDivElement>(null);
const [selectedColors, setSelectedColors] = useState<string[]>([]);
const scrollToEnd = () => {
containerRef.current?.scrollTo({
left: containerRef.current.scrollWidth,
behavior: "smooth",
});
};
const handleAddColor = (color: string) => {
flushSync(() => {
setSelectedColors((prev) => [...prev.filter((c) => c !== color), color]);
});
scrollToEnd();
};
const handleRemoveColor = (color: string) => {
flushSync(() => {
setSelectedColors((prev) => prev.filter((c) => c !== color));
});
scrollToEnd();
};
return (
<div className="w-300px">
<HvLabel
label="Dropdown with tags on header"
id="tags-dropdown"
showGutter
/>
<HvBaseDropdown
aria-labelledby="tags-dropdown"
onContainerCreation={() => focusTarget.current?.focus()}
placeholder={
selectedColors.length ? (
<div
className="flex gap-xs overflow-scroll px-1px h-full items-center"
ref={containerRef}
>
{selectedColors.map((color) => (
<HvTag
key={color}
label={color}
onDelete={() => handleRemoveColor(color)}
/>
))}
</div>
) : (
"Select colors from the dropdown..."
)
}
>
<div ref={focusTarget} tabIndex={-1} />
<HvPanel className="flex gap-xs flex-wrap">
{colors
.filter((color) => !selectedColors.includes(color))
.map((color) => (
<HvTag
key={color}
label={color}
onClick={() => handleAddColor(color)}
/>
))}
</HvPanel>
</HvBaseDropdown>
</div>
);
}import { useEffect, useRef, useState } from "react";
import { ClickAwayListener, Popper } from "@mui/base";
import {
HvAdornment,
HvPanel,
HvTag,
HvTagsInput,
HvTypography,
} from "@hitachivantara/uikit-react-core";
import { DropDownXS } from "@hitachivantara/uikit-react-icons";
const colors = [
"Blue",
"Red",
"Green",
"Yellow",
"Purple",
"White",
"Black",
"Orange",
"Pink",
"Brown",
"Gray",
"Cyan",
"Magenta",
"Lime",
"Teal",
"Lavender",
];
const lastUsed = ["Blue", "Red", "Green"];
export default function Demo() {
const containerRef = useRef<HTMLDivElement>(null);
const [selectedColors, setSelectedColors] = useState<string[]>([]);
const [open, setOpen] = useState(false);
useEffect(() => {
if (containerRef.current) {
containerRef.current.scrollLeft = containerRef.current.scrollWidth;
}
}, [selectedColors]);
const handleAddColor = (color: string) => {
setSelectedColors((prev) => {
const newColors = prev.filter((c) => c !== color);
return newColors.length ? [...newColors, color] : [color];
});
};
const handleRemoveColor = (color: string) => {
setSelectedColors((prev) => prev.filter((c) => c !== color));
};
return (
<div className="w-300px">
<HvTagsInput
label="TagsInput with Dropdown"
placeholder="Enter tags or select from the dropdown..."
ref={containerRef}
onChange={(event, value) => {
setSelectedColors(
value.map((v) => (typeof v === "string" ? v : (v.label as string))),
);
}}
onDelete={(_, value) => {
handleRemoveColor(value as string);
}}
value={selectedColors}
commitTagOn={["Comma", "Enter"]}
endAdornment={
<HvAdornment
tabIndex={0}
icon={<DropDownXS rotate={open} size="xs" />}
onClick={() => setOpen((o) => !o)}
/>
}
classes={{
tagsList: "h-32px pr-0! overflow-hidden",
}}
/>
<Popper
anchorEl={containerRef.current}
open={open}
placement="bottom-start"
>
<ClickAwayListener onClickAway={() => setOpen(false)}>
<HvPanel className="grid gap-xs w-300px my-2px border rounded-large">
<HvTypography variant="caption1">Last Used:</HvTypography>
<div className="flex gap-xs">
{lastUsed.map((color, idx) => (
<HvTag
autoFocus={idx === 0}
key={color}
label={color}
onClick={() => handleAddColor(color)}
/>
))}
</div>
<HvTypography variant="caption1">More colors:</HvTypography>
<div className="flex flex-wrap gap-xs">
{colors
.filter(
(color) =>
!selectedColors.includes(color) &&
!lastUsed.includes(color),
)
.map((color) => (
<HvTag
key={color}
label={color}
onClick={() => handleAddColor(color)}
/>
))}
</div>
</HvPanel>
</ClickAwayListener>
</Popper>
</div>
);
}Inline Editable Text
Name:John Doe
import { useRef, useState } from "react";
import {
HvBaseInput,
HvButton,
HvIconButton,
HvTypography,
} from "@hitachivantara/uikit-react-core";
import { Check, Close, Edit } from "@hitachivantara/uikit-react-icons";
export default function Demo() {
const [editing, setEditing] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const [cachedValue, setCachedValue] = useState("John Doe");
return editing ? (
<form
className="flex items-center gap-xs w-260px"
onSubmit={(evt) => {
evt.preventDefault();
setCachedValue(inputRef.current?.value || "");
setEditing(false);
}}
onReset={() => setEditing(false)}
>
<HvBaseInput ref={inputRef} autoFocus defaultValue={cachedValue} />
<HvIconButton type="reset" size="xs" variant="negative" title="Cancel">
<Close />
</HvIconButton>
<HvIconButton type="submit" size="xs" variant="positive" title="Save">
<Check />
</HvIconButton>
</form>
) : (
<div className="flex items-center gap-xs w-260px">
<span className="capitalize">Name:</span>
<HvTypography variant="label">{cachedValue}</HvTypography>
<HvButton icon title="Edit" onClick={() => setEditing(true)}>
<Edit />
</HvButton>
</div>
);
}