Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tricky-wolves-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lunar-js/lunar': minor
---

feat: add visually hidden style utility
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const input = style([
'&[aria-invalid="true"]': {
borderColor: themeContract.colors.border.error,
},
...withCustomOutline(themeContract.colors.shadow.destructive, '&[aria-invalid="true"]'),
...withCustomOutline(themeContract.colors.shadow.destructive, '&[aria-invalid="true"]:focus-visible'),
},

'::file-selector-button': {
Expand Down
7 changes: 6 additions & 1 deletion packages/lunar/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ export { useDialog } from './hooks/dialog.js';
/**
* Styling Utility Exports
*/
export { withCustomOutline, withSafeTransition, withBreakpoint } from './themes/styles/utilities.js';
export {
withCustomOutline,
withSafeTransition,
withBreakpoint,
withVisuallyHidden,
} from './themes/styles/utilities.js';

export { BREAKPOINT__SM, BREAKPOINT__MD, BREAKPOINT__LG } from './constants/theming.js';
export {
Expand Down
75 changes: 59 additions & 16 deletions packages/lunar/src/themes/styles/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,10 @@ const withSafeTransition = (styles: StyleRule): StyleRule => ({

/**
* Helper function to create a custom focus outline style object.
* Returns a style object with focus-visible outline that follows the design system's outline pattern.
* Supports an optional selector parameter for targeting specific pseudo-selectors or child elements.
*
* This function returns a style object that must be used with vanilla-extract's `style()` function or `recipe()` function.
*
* @param outlineColor - The color for the focus outline (e.g., '#0066cc', 'rgb(255, 0, 0)', CSS custom properties)
* @param selector - Optional selector prefix for the focus-visible state (e.g., '&', '& > button', defaults to '')
* @returns A style object with custom focus outline styling that uses dynamic selector keys
* @param selector - Optional CSS selector for the outline state (e.g., ':focus-visible', '&:hover', '& > button:focus-visible', defaults to ':focus-visible')
* @returns A style object with custom focus outline styling that uses the provided selector
*
* @example
* import { style } from '@vanilla-extract/css';
Expand All @@ -58,10 +54,11 @@ const withSafeTransition = (styles: StyleRule): StyleRule => ({
* ]);
*
* // Example with custom selector
* const parentWithFocusableChild = style([
* withCustomOutline('#0066cc', '& > button'),
* const hoverOutlineButton = style([
* withCustomOutline('#00cc66', ':hover'),
* {
* padding: '12px'
* padding: '8px 16px',
* background: 'white'
* }
* ]);
*
Expand All @@ -78,8 +75,8 @@ const withSafeTransition = (styles: StyleRule): StyleRule => ({
* }
* });
*/
const withCustomOutline = (outlineColor: string, selector = ''): Record<`${string}:focus-visible`, CSSProperties> => ({
[`${selector}:focus-visible`]: {
const withCustomOutline = (outlineColor: string, selector = ':focus-visible'): Record<string, CSSProperties> => ({
[selector]: {
boxShadow: `0px 0px 0px 0px, ${COLORS__PURE.transparent} 0px 0px 0px 0px, ${COLORS__PURE.transparent} 0px 0px 0px 0px, ${outlineColor} 0px 0px 0px 3px, ${outlineColor} 0px 1px 2px 0px`,
outline: '2px solid transparent',
outlineOffset: '2px',
Expand All @@ -88,10 +85,6 @@ const withCustomOutline = (outlineColor: string, selector = ''): Record<`${strin

/**
* Helper function to create a responsive style object with breakpoint media queries.
* Returns a style object that applies the provided styles only when the viewport width
* meets or exceeds the specified breakpoint value.
*
* This function returns a style object that must be used with vanilla-extract's `style()` function or `recipe()` function.
*
* @param breakpoint - The minimum viewport width for the media query (e.g., '768px', '1024px', '48rem')
* @param styles - CSS properties object to apply at the breakpoint (e.g., { fontSize: '1.5rem', padding: '24px' })
Expand Down Expand Up @@ -137,4 +130,54 @@ const withBreakpoint = (breakpoint: string, styles: CSSProperties): StyleRule =>
},
});

export { withSafeTransition, withCustomOutline, withBreakpoint };
/**
* Helper function to create a visually hidden style object for screen reader accessibility.
* Returns a style object that hides content visually while keeping it accessible to screen readers.
* This is useful for providing descriptive text, skip links, or other content that should only
* be available to assistive technologies.
*
* @returns A style object that visually hides content while maintaining screen reader accessibility
*
* @example
* import { style } from '@vanilla-extract/css';
* import { recipe } from '@vanilla-extract/recipes';
* import { withVisuallyHidden } from './utilities.css.ts';
*
* const skipLink = style([
* withVisuallyHidden(),
* {
* // Additional styles can be added here
* zIndex: 1000
* }
* ]);
*
* const srOnly = style([
* withVisuallyHidden()
* ]);
*
* const buttonWithHiddenText = recipe({
* base: [
* withVisuallyHidden(),
* {
* padding: '8px 12px',
* background: 'blue'
* }
* ],
* variants: {
* // variant styles
* }
* });
*/
const withVisuallyHidden = (): StyleRule => ({
position: 'absolute',
width: '1px',
height: '1px',
padding: '0',
margin: '-1px',
overflow: 'hidden',
clip: 'rect(0, 0, 0, 0)',
whiteSpace: 'nowrap',
borderWidth: '0',
});

export { withSafeTransition, withCustomOutline, withBreakpoint, withVisuallyHidden };