);
}
diff --git a/apps/docs/content/docs/api-references/heroui-provider.mdx b/apps/docs/content/docs/api-references/heroui-provider.mdx
index 1c64b93854..baa5f71784 100644
--- a/apps/docs/content/docs/api-references/heroui-provider.mdx
+++ b/apps/docs/content/docs/api-references/heroui-provider.mdx
@@ -147,9 +147,9 @@ interface AppProviderProps {
`labelPlacement`
-- **Description**: Determines the position where label should appear, such as inside, outside or outside-left of the component.
+- **Description**: Determines the position where label should appear, such as inside, outside, outside-left or outside-top of the component.
- **Type**: `string` | `undefined`
-- **Possible Values**: `inside` | `outside` | `outside-left` | `undefined`
+- **Possible Values**: `inside` | `outside` | `outside-left` | `outside-top` | `undefined`
- **Default**: `undefined`
diff --git a/apps/docs/content/docs/components/autocomplete.mdx b/apps/docs/content/docs/components/autocomplete.mdx
index 9bb459ab6f..209ca2ad48 100644
--- a/apps/docs/content/docs/components/autocomplete.mdx
+++ b/apps/docs/content/docs/components/autocomplete.mdx
@@ -102,7 +102,7 @@ all available options, but users won't be able to select any of the listed optio
### Label Placements
-You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside` or `outside-left`.
+You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside`, `outside-left` or `outside-top`.
@@ -370,7 +370,7 @@ import {parseZonedDateTime} from "@internationalized/date";
},
{
attribute: "labelPlacement",
- type: "inside | outside | outside-left",
+ type: "inside | outside | outside-left | outside-top",
description: "The position of the label.",
default: "inside"
},
diff --git a/apps/docs/content/docs/components/date-picker.mdx b/apps/docs/content/docs/components/date-picker.mdx
index 6cd73efbe8..baf57e4b25 100644
--- a/apps/docs/content/docs/components/date-picker.mdx
+++ b/apps/docs/content/docs/components/date-picker.mdx
@@ -59,7 +59,7 @@ DatePickers combine a DateInput and a Calendar popover to allow users to enter o
### Label Placements
-You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside` or `outside-left`.
+You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside`, `outside-left` or `outside-top`.
@@ -407,7 +407,7 @@ import {I18nProvider} from "@react-aria/i18n";
},
{
attribute: "labelPlacement",
- type: "inside | outside | outside-left",
+ type: "inside | outside | outside-left | outside-top",
description: "The position of the label.",
default: "inside"
},
diff --git a/apps/docs/content/docs/components/date-range-picker.mdx b/apps/docs/content/docs/components/date-range-picker.mdx
index 6bec315131..04f59c1ec0 100644
--- a/apps/docs/content/docs/components/date-range-picker.mdx
+++ b/apps/docs/content/docs/components/date-range-picker.mdx
@@ -80,7 +80,7 @@ By default, when pressing the next or previous buttons, pagination will advance
### Label Placements
-You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside` or `outside-left`.
+You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside`, `outside-left` or `outside-top`.
@@ -478,7 +478,7 @@ You can customize the `DateRangePicker` component by passing custom Tailwind CSS
},
{
attribute: "labelPlacement",
- type: "inside | outside | outside-left",
+ type: "inside | outside | outside-left | outside-top",
description: "The position of the label.",
default: "inside"
},
diff --git a/apps/docs/content/docs/components/input.mdx b/apps/docs/content/docs/components/input.mdx
index 751f4f9234..0b584ef335 100644
--- a/apps/docs/content/docs/components/input.mdx
+++ b/apps/docs/content/docs/components/input.mdx
@@ -353,7 +353,7 @@ In case you need to customize the input even further, you can use the `useInput`
},
{
attribute: "labelPlacement",
- type: "inside | outside | outside-left",
+ type: "inside | outside | outside-left | outside-top",
description: "The position of the label.",
default: "inside"
},
diff --git a/apps/docs/content/docs/components/number-input.mdx b/apps/docs/content/docs/components/number-input.mdx
index a027e2aec1..a79da5cd7e 100644
--- a/apps/docs/content/docs/components/number-input.mdx
+++ b/apps/docs/content/docs/components/number-input.mdx
@@ -65,12 +65,16 @@ the end of the label and the input will be required.
### Label Placements
-You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside` or `outside-left`.
+You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside`, `outside-left` or `outside-top`.
> **Note**: If the `label` is not passed, the `labelPlacement` property will be `outside` by default.
+> **Note**: If the `labelPlacement` is `outside`, `label` is outside only when a placeholder is provided.
+
+> **Note**: If the `labelPlacement` is `outside-top` or `outside-left`, `label` is outside even if a placeholder is not provided.
+
### Clear Button
If you pass the `isClearable` property to the input, it will have a clear button at the
@@ -373,7 +377,7 @@ You can customize the `NumberInput` component by passing custom Tailwind CSS cla
},
{
attribute: "labelPlacement",
- type: "inside | outside | outside-left",
+ type: "inside | outside | outside-left | outside-top",
description: "The position of the label.",
default: "inside"
},
diff --git a/apps/docs/content/docs/components/select.mdx b/apps/docs/content/docs/components/select.mdx
index f7fa0ee054..22c2c4fe5a 100644
--- a/apps/docs/content/docs/components/select.mdx
+++ b/apps/docs/content/docs/components/select.mdx
@@ -97,12 +97,16 @@ the end of the label and the select will be required.
### Label Placements
-You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside` or `outside-left`.
+You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside`, `outside-left` or `outside-top`.
> **Note**: If the `label` is not passed, the `labelPlacement` property will be `outside` by default.
+> **Note**: If the `labelPlacement` is `outside`, `label` is outside only when a placeholder is provided.
+
+> **Note**: If the `labelPlacement` is `outside-top` or `outside-left`, `label` is outside even if a placeholder is not provided.
+
### Start Content
You can use the `startContent` properties to add content to the start of the select.
@@ -476,7 +480,7 @@ If you need to submit a specific `value` instead of the `key` during form submis
},
{
attribute: "labelPlacement",
- type: "inside | outside | outside-left",
+ type: "inside | outside | outside-left | outside-top",
description: "The position of the label.",
default: "inside"
},
diff --git a/apps/docs/content/docs/components/time-input.mdx b/apps/docs/content/docs/components/time-input.mdx
index 69546b96da..bf29aead18 100644
--- a/apps/docs/content/docs/components/time-input.mdx
+++ b/apps/docs/content/docs/components/time-input.mdx
@@ -89,11 +89,13 @@ You can also pass an error message as a function. This allows for dynamic error
-### Label Placement
+### Label Placements
-The label's overall position relative to the element it is labeling.
+You can change the position of the label by setting the `labelPlacement` property to `inside`, `outside`, `outside-left` or `outside-top`.
-
+
+
+> **Note**: If the `label` is not passed, the `labelPlacement` property will be `outside` by default.
### Start Content
@@ -279,7 +281,7 @@ By default, `TimeInput` displays times in either 12 or 24 hour hour format depen
},
{
attribute: "labelPlacement",
- type: "inside | outside | outside-left",
+ type: "inside | outside | outside-left | outside-top",
description: "The position of the label.",
default: "inside"
},
diff --git a/packages/components/autocomplete/__tests__/autocomplete.test.tsx b/packages/components/autocomplete/__tests__/autocomplete.test.tsx
index f55ecdf0d9..e42642be65 100644
--- a/packages/components/autocomplete/__tests__/autocomplete.test.tsx
+++ b/packages/components/autocomplete/__tests__/autocomplete.test.tsx
@@ -8,6 +8,7 @@ import userEvent from "@testing-library/user-event";
import {spy, shouldIgnoreReactWarning} from "@heroui/test-utils";
import {useForm} from "react-hook-form";
import {Form} from "@heroui/form";
+import {HeroUIProvider} from "@heroui/system";
import {Autocomplete, AutocompleteItem, AutocompleteSection} from "../src";
import {Modal, ModalContent, ModalBody, ModalHeader, ModalFooter} from "../../modal/src";
@@ -860,6 +861,55 @@ describe("Autocomplete", () => {
});
});
});
+
+ describe("Autocomplete with HeroUIProvider context", () => {
+ it("should inherit labelPlacement from HeroUIProvider", () => {
+ const {container} = render(
+
+
+ {(item) => {item.label}}
+
+ ,
+ );
+
+ const label = container.querySelector("label");
+
+ expect(label).toBeTruthy();
+ expect(label?.className).toMatch(/translate-y.*100%/);
+ });
+
+ it("should prioritize labelPlacement prop over HeroUIProvider context", () => {
+ const {container} = render(
+
+
+ {(item) => {item.label}}
+
+ ,
+ );
+
+ const label = container.querySelector("label");
+
+ expect(label?.className).not.toMatch(/translate-y.*100%/);
+ });
+
+ it("should inherit labelPlacement='outside-top' from HeroUIProvider", () => {
+ const {container} = render(
+
+
+ {(item) => {item.label}}
+
+ ,
+ );
+
+ const label = container.querySelector("label");
+ const mainWrapper = container.querySelector("[data-slot=main-wrapper]");
+
+ expect(label).toBeTruthy();
+ // outside-top uses flex-col on mainWrapper and relative label (no translate-y)
+ expect(mainWrapper).toHaveClass("flex-col");
+ expect(label?.className).not.toMatch(/translate-y.*100%/);
+ });
+ });
});
describe("Autocomplete with React Hook Form", () => {
diff --git a/packages/components/date-input/__tests__/date-input.test.tsx b/packages/components/date-input/__tests__/date-input.test.tsx
index 8c19867e0f..c8178d3325 100644
--- a/packages/components/date-input/__tests__/date-input.test.tsx
+++ b/packages/components/date-input/__tests__/date-input.test.tsx
@@ -7,6 +7,7 @@ import {fireEvent, render} from "@testing-library/react";
import {CalendarDate, CalendarDateTime, ZonedDateTime} from "@internationalized/date";
import {pointerMap, triggerPress} from "@heroui/test-utils";
import userEvent from "@testing-library/user-event";
+import {HeroUIProvider} from "@heroui/system";
import {DateInput as DateInputBase} from "../src";
@@ -338,4 +339,55 @@ describe("DateInput", () => {
expect(input).toHaveValue("2020-02-03");
});
});
+ describe("DateInput with HeroUIProvider context", () => {
+ it("should inherit labelPlacement from HeroUIProvider", () => {
+ const labelContent = "Test date input label";
+
+ render(
+
+
+ ,
+ );
+
+ const label = document.querySelector("[data-slot=label]");
+ const group = document.querySelector("[data-slot=input-wrapper]");
+
+ expect(label).toHaveTextContent(labelContent);
+ expect(group).not.toHaveTextContent(labelContent);
+ });
+
+ it("should prioritize labelPlacement prop over HeroUIProvider context", () => {
+ const labelContent = "Test date input label";
+
+ render(
+
+
+ ,
+ );
+
+ const label = document.querySelector("[data-slot=label]");
+ const group = document.querySelector("[data-slot=input-wrapper]");
+
+ expect(label).toHaveTextContent(labelContent);
+ expect(group).toHaveTextContent(labelContent);
+ });
+
+ it("should inherit labelPlacement='outside-top' from HeroUIProvider", () => {
+ const labelContent = "Test date input label";
+
+ const {container} = render(
+
+
+ ,
+ );
+
+ const label = document.querySelector("[data-slot=label]");
+ const group = document.querySelector("[data-slot=input-wrapper]");
+ const base = container.querySelector("[data-slot=base]");
+
+ expect(label).toHaveTextContent(labelContent);
+ expect(group).not.toHaveTextContent(labelContent);
+ expect(base).toHaveClass("flex-col");
+ });
+ });
});
diff --git a/packages/components/date-input/__tests__/time-input.test.tsx b/packages/components/date-input/__tests__/time-input.test.tsx
index 95896615f5..351cebc5bf 100644
--- a/packages/components/date-input/__tests__/time-input.test.tsx
+++ b/packages/components/date-input/__tests__/time-input.test.tsx
@@ -7,6 +7,7 @@ import {fireEvent, render} from "@testing-library/react";
import {Time, ZonedDateTime} from "@internationalized/date";
import {pointerMap, triggerPress} from "@heroui/test-utils";
import userEvent from "@testing-library/user-event";
+import {HeroUIProvider} from "@heroui/system";
import {TimeInput as TimeInputBase} from "../src";
@@ -443,4 +444,56 @@ describe("TimeInput", () => {
expect(document.querySelector("[data-slot=error-message]")).toBeVisible();
});
});
+
+ describe("TimeInput with HeroUIProvider context", () => {
+ it("should inherit labelPlacement from HeroUIProvider", () => {
+ const labelContent = "Test time input label";
+
+ render(
+
+
+ ,
+ );
+
+ const label = document.querySelector("[data-slot=label]");
+ const group = document.querySelector("[data-slot=input-wrapper]");
+
+ expect(label).toHaveTextContent(labelContent);
+ expect(group).not.toHaveTextContent(labelContent);
+ });
+
+ it("should prioritize labelPlacement prop over HeroUIProvider context", () => {
+ const labelContent = "Test time input label";
+
+ render(
+
+
+ ,
+ );
+
+ const label = document.querySelector("[data-slot=label]");
+ const group = document.querySelector("[data-slot=input-wrapper]");
+
+ expect(label).toHaveTextContent(labelContent);
+ expect(group).toHaveTextContent(labelContent);
+ });
+
+ it("should inherit labelPlacement='outside-top' from HeroUIProvider", () => {
+ const labelContent = "Test time input label";
+
+ const {container} = render(
+
+
+ ,
+ );
+
+ const label = document.querySelector("[data-slot=label]");
+ const group = document.querySelector("[data-slot=input-wrapper]");
+ const base = container.querySelector("[data-slot=base]");
+
+ expect(label).toHaveTextContent(labelContent);
+ expect(group).not.toHaveTextContent(labelContent);
+ expect(base).toHaveClass("flex-col");
+ });
+ });
});
diff --git a/packages/components/date-input/package.json b/packages/components/date-input/package.json
index 4b744eb333..80a58a9046 100644
--- a/packages/components/date-input/package.json
+++ b/packages/components/date-input/package.json
@@ -35,7 +35,7 @@
},
"peerDependencies": {
"@heroui/system": ">=2.4.18",
- "@heroui/theme": ">=2.4.17",
+ "@heroui/theme": ">=2.4.23",
"react": ">=18 || >=19.0.0-rc.0",
"react-dom": ">=18 || >=19.0.0-rc.0"
},
diff --git a/packages/components/date-input/src/use-date-input.ts b/packages/components/date-input/src/use-date-input.ts
index f06afaab89..27e41a7d59 100644
--- a/packages/components/date-input/src/use-date-input.ts
+++ b/packages/components/date-input/src/use-date-input.ts
@@ -202,7 +202,10 @@ export function useDateInput(originalProps: UseDateInputPro
label,
});
- const shouldLabelBeOutside = labelPlacement === "outside" || labelPlacement === "outside-left";
+ const shouldLabelBeOutside =
+ labelPlacement === "outside" ||
+ labelPlacement === "outside-left" ||
+ labelPlacement === "outside-top";
const slots = useMemo(
() =>
diff --git a/packages/components/date-input/src/use-time-input.ts b/packages/components/date-input/src/use-time-input.ts
index b13b8670db..e3f66e09c8 100644
--- a/packages/components/date-input/src/use-time-input.ts
+++ b/packages/components/date-input/src/use-time-input.ts
@@ -139,7 +139,10 @@ export function useTimeInput(originalProps: UseTimeInputPro
label,
});
- const shouldLabelBeOutside = labelPlacement === "outside" || labelPlacement === "outside-left";
+ const shouldLabelBeOutside =
+ labelPlacement === "outside" ||
+ labelPlacement === "outside-left" ||
+ labelPlacement === "outside-top";
const slots = useMemo(
() =>
diff --git a/packages/components/date-input/stories/date-input.stories.tsx b/packages/components/date-input/stories/date-input.stories.tsx
index 3a9e56c005..36f74b4b7f 100644
--- a/packages/components/date-input/stories/date-input.stories.tsx
+++ b/packages/components/date-input/stories/date-input.stories.tsx
@@ -51,7 +51,7 @@ export default {
control: {
type: "select",
},
- options: ["inside", "outside", "outside-left"],
+ options: ["inside", "outside", "outside-left", "outside-top"],
},
isDisabled: {
control: {
@@ -96,6 +96,7 @@ const LabelPlacementTemplate = (args: DateInputProps) => (
+