diff --git a/apps/website/pages/foundations/elevation.tsx b/apps/website/pages/foundations/elevation.tsx new file mode 100644 index 000000000..78151e4e3 --- /dev/null +++ b/apps/website/pages/foundations/elevation.tsx @@ -0,0 +1,13 @@ +import Head from "next/head"; +import ElevationPage from "screens/foundations/elevation/ElevationPage"; + +const Elevation = () => ( + <> + + Elevation — Halstack Design System + + + +); + +export default Elevation; diff --git a/apps/website/screens/common/pagesList.ts b/apps/website/screens/common/pagesList.ts index 4417ce237..c6d771d1b 100644 --- a/apps/website/screens/common/pagesList.ts +++ b/apps/website/screens/common/pagesList.ts @@ -40,6 +40,7 @@ const principlesLinks: LinkDetails[] = [ const foundationsLinks: LinkDetails[] = [ { label: "Color", path: "/foundations/color" }, + { label: "Elevation", path: "/foundations/elevation" }, { label: "Height", path: "/foundations/height" }, { label: "Iconography", path: "/foundations/iconography" }, { label: "Spacing", path: "/foundations/spacing" }, diff --git a/apps/website/screens/components/container/code/examples/listbox.tsx b/apps/website/screens/components/container/code/examples/listbox.tsx index df8a4d9ff..1b490b42b 100644 --- a/apps/website/screens/components/container/code/examples/listbox.tsx +++ b/apps/website/screens/components/container/code/examples/listbox.tsx @@ -13,7 +13,7 @@ const code = `() => { width: "var(--border-width-s)" }} borderRadius="var(--border-radius-s)" - boxShadow="var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) var(--shadow-light)" + boxShadow="var(--shadow-200)" boxSizing="border-box" maxHeight="304px" overflow={{ x: "hidden", y: "auto" }} diff --git a/apps/website/screens/foundations/elevation/ElevationPage.tsx b/apps/website/screens/foundations/elevation/ElevationPage.tsx new file mode 100644 index 000000000..0cf6c76d8 --- /dev/null +++ b/apps/website/screens/foundations/elevation/ElevationPage.tsx @@ -0,0 +1,180 @@ +import { DxcHeading, DxcFlex, DxcTable, DxcParagraph, DxcBulletedList } from "@dxc-technology/halstack-react"; +import Image from "@/common/Image"; +import Code from "@/common/Code"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import PageHeading from "@/common/PageHeading"; +import DocFooter from "@/common/DocFooter"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import shadows from "./images/shadows.jpg"; +import Figure from "@/common/Figure"; + +const sections = [ + { + title: "Introduction", + content: ( + <> + + Shadows are a fundamental part of visual design systems. In Halstack, we use them, along with colors, to{" "} + create depth and layering on the interface. They help users distinguish between surfaces, + understand component hierarchy, and focus their attention on key elements. + + + By simulating how light interacts with objects, shadows reinforce a clear spatial structure in digital + interfaces, much like in the physical world. Whether it's emphasizing a modal over a background or giving + subtle prominence to a card, elevation contributes both aesthetically and functionally to the user experience. + + + ), + }, + { + title: "Shadow tokens", + content: ( + <> + + Halstack provides a set of predefined shadow tokens optimized for clarity and performance across our products. + Each token corresponds to a specific elevation level with calibrated values for offset, blur, and color + transparency. + + + + + Token + X position + Y position + Blur + Spread + color + + + + + + shadow-100 + + 0px + 2px + 2px + 0px + + color-grey-400-a + + + + + shadow-200 + + 0px + 12px + 12px + 0px + + color-grey-300-a + + + + + shadow-300 + + 0px + 24px + 24px + 0px + + color-grey-300-a + + + + + shadow-400 + + 0px + 48px + 48px + 0px + + color-grey-300-a + + + + +
+ Halstack shadows applied to containers +
+ + ), + }, + { + title: "Shadow guidelines by scale", + content: ( + <> + + Each shadow style is designed to serve a different level of emphasis or structural role in the UI. Below are + some typical use cases per shadow level: + + + + shadow-100: creates subtle separation from the background without drawing too much + attention, such as small UI elements like buttons, input fields, or lightweight cards. + + + shadow-200: signals a slight lift and draws more attention than shadow-100, especially + useful for elements that temporarily appear above the rest of the UI; such as cards, dashboard, popovers, or + dropdowns. + + + shadow-300: used for modals, bottom sheets, or floating panels; as it clearly separates + important, interactive components from the rest of the content. + + + shadow-400: provides the strongest visual depth to ensure clear hierarchy and focus in the + UI. A few examples where this shadow can be applied are full-screen overlays, onboarding dialogs, or focused + system alerts. + + + + ), + }, + { + title: "Best practices", + content: ( + + + Use elevation purposefully: shadows are not decorative. Apply them to communicate visual + hierarchy and component behavior. + + + Don’t overlay too much: avoid stacking multiple shadows or using high-intensity shadows on + too many components, as this leads to visual clutter. + + + Stay within the core scale: Use only the defined shadow tokens unless there's a validated + need for a custom elevation. + + + Avoid using shadows as borders: If you need to separate elements or define boundaries, use + spacing or a border token instead. + + + Consistency across themes: Our shadow tokens are designed to adapt well across themes and + backgrounds. Avoid tweaking individual values unless strictly necessary. + + + ), + }, +]; + +export default function ElevationPage() { + return ( + + + + + + + + + + + + ); +} diff --git a/apps/website/screens/foundations/elevation/images/shadows.jpg b/apps/website/screens/foundations/elevation/images/shadows.jpg new file mode 100644 index 000000000..b32bb34a8 Binary files /dev/null and b/apps/website/screens/foundations/elevation/images/shadows.jpg differ diff --git a/apps/website/screens/foundations/spacing/SpacingPage.tsx b/apps/website/screens/foundations/spacing/SpacingPage.tsx index 4c81e0507..9b30a3089 100644 --- a/apps/website/screens/foundations/spacing/SpacingPage.tsx +++ b/apps/website/screens/foundations/spacing/SpacingPage.tsx @@ -309,7 +309,7 @@ export default function SpacingPage() { - + ); } diff --git a/packages/lib/src/accordion/AccordionItem.tsx b/packages/lib/src/accordion/AccordionItem.tsx index 2be01b20a..ae4925998 100644 --- a/packages/lib/src/accordion/AccordionItem.tsx +++ b/packages/lib/src/accordion/AccordionItem.tsx @@ -12,8 +12,7 @@ const AccordionContainer = styled.div` flex-direction: column; background-color: var(--color-bg-neutral-lightest); border-radius: var(--border-radius-s); - box-shadow: var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) - var(--shadow-light); + box-shadow: var(--shadow-200); min-width: 280px; width: 100%; `; diff --git a/packages/lib/src/card/Card.tsx b/packages/lib/src/card/Card.tsx index df063d222..6de93bc71 100644 --- a/packages/lib/src/card/Card.tsx +++ b/packages/lib/src/card/Card.tsx @@ -26,11 +26,7 @@ const Card = styled.div<{ }`} border-radius: var(--border-radius-s); box-shadow: ${({ shadowDepth }) => - shadowDepth === 1 - ? "var(--shadow-low-x-position) var(--shadow-low-y-position) var(--shadow-low-blur) var(--shadow-low-spread) var(--shadow-dark)" - : shadowDepth === 2 - ? "var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) var(--shadow-light)" - : "none"}; + shadowDepth === 1 ? "var(--shadow-100)" : shadowDepth === 2 ? "var(--shadow-200)" : "none"}; `; const CardContainer = styled.div<{ diff --git a/packages/lib/src/container/Container.stories.tsx b/packages/lib/src/container/Container.stories.tsx index 7e4ade187..8846f18be 100644 --- a/packages/lib/src/container/Container.stories.tsx +++ b/packages/lib/src/container/Container.stories.tsx @@ -18,7 +18,7 @@ const Listbox = ({ suggestions = [] }: { suggestions: string[] }): JSX.Element = style: "var(--border-style-default)", }} borderRadius="var(--border-radius-s)" - boxShadow="var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) var(--shadow-light)" + boxShadow="var(--shadow-200)" boxSizing="border-box" maxHeight="304px" overflow={{ x: "hidden", y: "auto" }} diff --git a/packages/lib/src/date-input/DatePicker.tsx b/packages/lib/src/date-input/DatePicker.tsx index 30a308090..fb5b8f45c 100644 --- a/packages/lib/src/date-input/DatePicker.tsx +++ b/packages/lib/src/date-input/DatePicker.tsx @@ -11,8 +11,7 @@ import { HalstackLanguageContext } from "../HalstackContext"; const DatePickerContainer = styled.div` padding: var(--spacing-padding-m) var(--spacing-padding-xs) var(--spacing-padding-xs) var(--spacing-padding-xs); background-color: var(--color-bg-neutral-lightest); - box-shadow: var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) - var(--shadow-light); + box-shadow: var(--shadow-200); border: var(--border-width-s) var(--border-style-default) var(--border-color-neutral-medium); border-radius: var(--border-radius-s); width: fit-content; diff --git a/packages/lib/src/date-input/YearPicker.tsx b/packages/lib/src/date-input/YearPicker.tsx index 0579dc3fb..b08f0cd54 100644 --- a/packages/lib/src/date-input/YearPicker.tsx +++ b/packages/lib/src/date-input/YearPicker.tsx @@ -12,8 +12,7 @@ const YearPickerContainer = styled.div` overflow-y: scroll; width: 292px; height: 312px; - box-shadow: var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) - var(--shadow-light); + box-shadow: var(--shadow-200); `; const YearPickerButton = styled.button<{ diff --git a/packages/lib/src/dialog/Dialog.tsx b/packages/lib/src/dialog/Dialog.tsx index 6e9e16b4a..74a219234 100644 --- a/packages/lib/src/dialog/Dialog.tsx +++ b/packages/lib/src/dialog/Dialog.tsx @@ -43,7 +43,7 @@ const Dialog = styled.div<{ closable: DialogPropsType["closable"] }>` border-radius: 4px; background-color: var(--color-bg-neutral-lightest); ${(props) => props.closable && "min-height: 72px;"} - box-shadow: var(--shadow-low-x-position) var(--shadow-low-y-position) var(--shadow-low-blur) var(--shadow-low-spread) var(--shadow-dark); + box-shadow: var(--shadow-100); @media (max-width: ${responsiveSizes.medium}rem) { max-width: 92%; diff --git a/packages/lib/src/dropdown/DropdownMenu.tsx b/packages/lib/src/dropdown/DropdownMenu.tsx index 3523af212..3473196bc 100644 --- a/packages/lib/src/dropdown/DropdownMenu.tsx +++ b/packages/lib/src/dropdown/DropdownMenu.tsx @@ -11,8 +11,7 @@ const DropdownMenuContainer = styled.ul` margin: 0; background-color: var(--color-bg-neutral-lightest); border-radius: var(--border-radius-s); - box-shadow: var(--shadow-low-x-position) var(--shadow-low-y-position) var(--shadow-low-blur) var(--shadow-low-spread) - var(--shadow-dark); + box-shadow: var(--shadow-100); outline: none; overflow-y: auto; z-index: var(--z-dropdown); diff --git a/packages/lib/src/select/Listbox.tsx b/packages/lib/src/select/Listbox.tsx index 1294a9a74..1244232e7 100644 --- a/packages/lib/src/select/Listbox.tsx +++ b/packages/lib/src/select/Listbox.tsx @@ -15,8 +15,7 @@ const ListboxContainer = styled.div` background-color: var(--color-bg-neutral-lightest); border: var(--border-width-s) var(--border-style-default) var(--border-color-neutral-medium); border-radius: var(--border-radius-s); - box-shadow: var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) - var(--shadow-light); + box-shadow: var(--shadow-200); color: var(--color-fg-neutral-dark); font-family: var(--typography-font-family); font-size: var(--typography-label-m); diff --git a/packages/lib/src/styles/variables.css b/packages/lib/src/styles/variables.css index 6107773cc..2289d9b56 100644 --- a/packages/lib/src/styles/variables.css +++ b/packages/lib/src/styles/variables.css @@ -268,8 +268,6 @@ --color-fg-warning-medium: var(--color-orange-500); --color-fg-warning-strong: var(--color-orange-600); --color-fg-warning-stronger: var(--color-orange-800); - --shadow-dark: var(--color-alpha-400-a); - --shadow-light: var(--color-alpha-300-a); --border-radius-none: var(--dimensions-0); --border-radius-xs: var(--dimensions-2); --border-radius-s: var(--dimensions-4); @@ -289,22 +287,10 @@ --height-xl: var(--dimensions-40); --height-xxl: var(--dimensions-48); --height-xxxl: var(--dimensions-56); - --shadow-high-spread: var(--dimensions-0); - --shadow-high-x-position: var(--dimensions-0); - --shadow-high-blur: var(--dimensions-24); - --shadow-high-y-position: var(--dimensions-24); - --shadow-higher-spread: var(--dimensions-0); - --shadow-higher-x-position: var(--dimensions-0); - --shadow-higher-blur: var(--dimensions-48); - --shadow-higher-y-position: var(--dimensions-48); - --shadow-low-spread: var(--dimensions-0); - --shadow-low-x-position: var(--dimensions-0); - --shadow-low-blur: var(--dimensions-2); - --shadow-low-y-position: var(--dimensions-2); - --shadow-mid-spread: var(--dimensions-0); - --shadow-mid-x-position: var(--dimensions-0); - --shadow-mid-blur: var(--dimensions-12); - --shadow-mid-y-position: var(--dimensions-12); + --shadow-100: var(--dimensions-0) var(--dimensions-2) var(--dimensions-2) var(--dimensions-0) var(--color-alpha-400-a); + --shadow-200: var(--dimensions-0) var(--dimensions-12) var(--dimensions-12) var(--dimensions-0) var(--color-alpha-300-a); + --shadow-300: var(--dimensions-0) var(--dimensions-24) var(--dimensions-24) var(--dimensions-0) var(--color-alpha-300-a); + --shadow-400: var(--dimensions-0) var(--dimensions-48) var(--dimensions-48) var(--dimensions-0) var(--color-alpha-300-a); --spacing-gap-none: var(--dimensions-0); --spacing-gap-xxs: var(--dimensions-2); --spacing-gap-xs: var(--dimensions-4); diff --git a/packages/lib/src/switch/Switch.tsx b/packages/lib/src/switch/Switch.tsx index 2cf68b113..5d3629c01 100644 --- a/packages/lib/src/switch/Switch.tsx +++ b/packages/lib/src/switch/Switch.tsx @@ -104,8 +104,7 @@ const Switch = styled.span<{ checked: SwitchPropsType["checked"]; disabled: Swit height: var(--height-s); background-color: var(--color-fg-neutral-bright); border-radius: 50%; - box-shadow: var(--shadow-low-x-position) var(--shadow-low-y-position) var(--shadow-low-blur) - var(--shadow-low-spread) var(--shadow-dark); + box-shadow: var(--shadow-100); transform: ${({ checked }) => checked && "translateX(20px)"}; transition: transform 0.2s ease-in-out; /* Thumb transform transition */ } diff --git a/packages/lib/src/text-input/Suggestions.tsx b/packages/lib/src/text-input/Suggestions.tsx index 9e9cfca5a..cd6563c32 100644 --- a/packages/lib/src/text-input/Suggestions.tsx +++ b/packages/lib/src/text-input/Suggestions.tsx @@ -13,8 +13,7 @@ const SuggestionsContainer = styled.div` background-color: var(--color-bg-neutral-lightest); border: var(--border-width-s) var(--border-style-default) var(--border-color-neutral-medium); border-radius: var(--border-radius-s); - box-shadow: var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) - var(--shadow-light); + box-shadow: var(--shadow-200); color: var(--color-fg-neutral-dark); font-family: var(--typography-font-family); font-size: var(--typography-label-m); diff --git a/packages/lib/src/toast/Toast.tsx b/packages/lib/src/toast/Toast.tsx index 8c7bfe98d..760fb5abc 100644 --- a/packages/lib/src/toast/Toast.tsx +++ b/packages/lib/src/toast/Toast.tsx @@ -40,8 +40,7 @@ const Toast = styled.output<{ semantic: ToastPropsType["semantic"]; isClosing: b width: fit-content; border-left: var(--border-width-m) var(--border-style-default) ${({ semantic }) => getSemantic(semantic).primaryColor}; border-radius: var(--border-radius-s); - box-shadow: var(--shadow-low-x-position) var(--shadow-low-y-position) var(--shadow-low-blur) var(--shadow-low-spread) - var(--shadow-dark); + box-shadow: var(--shadow-100); display: inline-flex; gap: var(--spacing-gap-l); justify-content: space-between;