# useKeyboardHeight

> A React Native hook that tracks keyboard visibility, height, and animation duration with cross-platform support and screen rotation handling.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/hooks/useKeyboardHeight
- Markdown: https://ui.ahmedbna.com/docs/hooks/useKeyboardHeight.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/useKeyboardHeight.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/useKeyboardHeight.json
- Install: `npx bna-ui add useKeyboardHeight`

---

## Installation

### CLI

```bash
npx bna-ui add useKeyboardHeight
```

### Manual

**1.** Copy and paste the following code into your project.

```ts
// hooks/useKeyboardHeight.ts
import { useState, useEffect, useRef } from 'react';
import {
  Keyboard,
  Platform,
  Dimensions,
  KeyboardEvent,
  EmitterSubscription,
} from 'react-native';

interface UseKeyboardHeightReturn {
  keyboardHeight: number;
  isKeyboardVisible: boolean;
  keyboardAnimationDuration: number;
}

export const useKeyboardHeight = (): UseKeyboardHeightReturn => {
  const [keyboardHeight, setKeyboardHeight] = useState<number>(0);
  const [isKeyboardVisible, setIsKeyboardVisible] = useState<boolean>(false);
  const [keyboardAnimationDuration, setKeyboardAnimationDuration] =
    useState<number>(0);

  // Store previous height to handle edge cases
  const previousHeightRef = useRef<number>(0);

  useEffect(() => {
    let showSubscription: EmitterSubscription;
    let hideSubscription: EmitterSubscription;

    // Determine which events to listen to based on platform
    const showEvent =
      Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
    const hideEvent =
      Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';

    // Handle keyboard show
    const handleKeyboardShow = (event: KeyboardEvent) => {
      const { height } = event.endCoordinates;
      const duration = event.duration;

      // Validate height - sometimes we get invalid values
      if (height && height > 0) {
        setKeyboardHeight(height);
        setIsKeyboardVisible(true);
        setKeyboardAnimationDuration(duration || 250); // Default duration if not provided
        previousHeightRef.current = height;
      }
    };

    // Handle keyboard hide
    const handleKeyboardHide = (event: KeyboardEvent) => {
      setKeyboardHeight(0);
      setIsKeyboardVisible(false);

      // Get animation duration (iOS provides this, Android might not)
      const duration = event.duration || (Platform.OS === 'ios' ? 250 : 200);
      setKeyboardAnimationDuration(duration);
    };

    // Add event listeners
    showSubscription = Keyboard.addListener(showEvent, handleKeyboardShow);
    hideSubscription = Keyboard.addListener(hideEvent, handleKeyboardHide);

    // Cleanup function
    return () => {
      showSubscription.remove();
      hideSubscription.remove();
    };
  }, []);

  // Additional effect to handle edge cases and screen rotation
  useEffect(() => {
    let dimensionSubscription: EmitterSubscription;

    const handleDimensionChange = () => {
      // If keyboard was visible and screen rotated, we might need to recalculate
      if (isKeyboardVisible && previousHeightRef.current > 0) {
        // On screen rotation, keyboard height might change
        // This is more relevant for tablets and landscape mode
        const screenHeight = Dimensions.get('window').height;
        const screenWidth = Dimensions.get('window').width;

        // Simple heuristic: if we're in landscape and had a keyboard,
        // the height might be different
        if (screenWidth > screenHeight && Platform.OS === 'ios') {
          // iOS landscape keyboard is typically shorter
          const estimatedLandscapeHeight = Math.min(
            previousHeightRef.current,
            screenHeight * 0.4
          );
          setKeyboardHeight(estimatedLandscapeHeight);
        }
      }
    };

    // Listen to dimension changes (rotation, split screen, etc.)
    dimensionSubscription = Dimensions.addEventListener(
      'change',
      handleDimensionChange
    );

    return () => {
      dimensionSubscription?.remove();
    };
  }, [isKeyboardVisible]);

  return {
    keyboardHeight,
    isKeyboardVisible,
    keyboardAnimationDuration,
  };
};
```

**2.** Update the import paths to match your project setup.

## Usage

```tsx
import { useKeyboardHeight } from '@/hooks/useKeyboardHeight';
```

```tsx
export function KeyboardAwareView({ children }) {
  const { keyboardHeight, isKeyboardVisible, keyboardAnimationDuration } =
    useKeyboardHeight();

  return (
    <View>
      <Text>Keyboard Height: {keyboardHeight}</Text>
      <Text>Keyboard Visible: {isKeyboardVisible ? 'Yes' : 'No'}</Text>
      <Text>Animation Duration: {keyboardAnimationDuration}ms</Text>
    </View>
  );
}
```

## API Reference

### useKeyboardHeight

Returns keyboard state information including height, visibility, and animation duration with cross-platform compatibility.

#### Parameters

This hook takes no parameters.

#### Returns

| Property                    | Type      | Description                                       |
| --------------------------- | --------- | ------------------------------------------------- |
| `keyboardHeight`            | `number`  | Current keyboard height in pixels (0 when hidden) |
| `isKeyboardVisible`         | `boolean` | Whether the keyboard is currently visible         |
| `keyboardAnimationDuration` | `number`  | Duration of keyboard animation in milliseconds    |

## Platform Differences

The hook handles platform-specific keyboard events automatically:

### iOS

- Uses `keyboardWillShow` and `keyboardWillHide` events for smoother animations
- Provides accurate animation duration from the system
- Approximates landscape mode keyboard height via an unmeasured heuristic (see below)
- Default animation duration: 250ms

### Android

- Uses `keyboardDidShow` and `keyboardDidHide` events
- May not always provide animation duration (fallback: 200ms)
- Less predictable keyboard heights in landscape mode

```tsx
// The hook automatically selects the right events
const showEvent =
  Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
const hideEvent =
  Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
```

## Advanced Features

### Screen Rotation Support

On rotation to landscape on iOS, the hook adjusts the reported keyboard
height using an **unmeasured heuristic** — `screenHeight * 0.4`, capped at
the last known portrait height — rather than a real measurement from the
system. Treat it as a rough approximation for that one case, not a fully
supported feature: it doesn't apply on Android, and it can be off for
specific devices or third-party keyboards.

```tsx
// Approximate landscape keyboard height adjustment on iOS only
if (screenWidth > screenHeight && Platform.OS === 'ios') {
  const estimatedLandscapeHeight = Math.min(
    previousHeightRef.current,
    screenHeight * 0.4 // heuristic, not a measured value
  );
  setKeyboardHeight(estimatedLandscapeHeight);
}
```

### Edge Case Handling

- **Invalid Heights**: Filters out invalid or zero keyboard heights
- **Previous Height Tracking**: Maintains reference to last valid height for calculations
- **Dimension Changes**: Responds to screen size changes and split-screen scenarios
- **Memory Cleanup**: Properly removes all event listeners on unmount

## Use Cases

This hook is perfect for:

- Creating keyboard-aware input forms and chat interfaces
- Adjusting scroll view content insets when keyboard appears
- Animating UI elements in sync with keyboard transitions
- Building responsive layouts that adapt to keyboard presence
- Implementing custom keyboard avoidance behaviors

## Best Practices

### Performance Optimization

Use the hook at the appropriate component level to minimize re-renders:

```tsx
// Good: Use in parent component that needs to adjust layout
function ChatScreen() {
  const { keyboardHeight, isKeyboardVisible } = useKeyboardHeight();

  return (
    <View style={{ flex: 1, paddingBottom: keyboardHeight }}>
      <MessageList />
      <ChatInput />
    </View>
  );
}

// Avoid: Using in multiple child components
function MessageItem() {
  const { keyboardHeight } = useKeyboardHeight(); // Unnecessary
  // ...
}
```

### Safe Area Compatibility

Combine with safe area insets for proper spacing:

```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export function SafeKeyboardView({ children }) {
  const { keyboardHeight, isKeyboardVisible } = useKeyboardHeight();
  const insets = useSafeAreaInsets();

  const bottomPadding = isKeyboardVisible ? keyboardHeight : insets.bottom;

  return (
    <View
      style={{
        flex: 1,
        paddingTop: insets.top,
        paddingBottom: bottomPadding,
        paddingLeft: insets.left,
        paddingRight: insets.right,
      }}
    >
      {children}
    </View>
  );
}
```

## Troubleshooting

### Common Issues

**Keyboard height is 0 on Android:**

- Ensure `android:windowSoftInputMode="adjustResize"` is set in your AndroidManifest.xml
- Check that your app is not using `android:windowSoftInputMode="adjustPan"`

**Inconsistent behavior in landscape mode:**

- The hook includes landscape detection and adjustment for iOS
- Android landscape keyboard behavior varies by device and keyboard app

**Animation timing doesn't match system keyboard:**

- iOS provides accurate animation duration from the system
- Android may require manual duration tuning based on your app's needs

### Debug Information

Add debug logging to understand keyboard behavior:

```tsx
export function DebugKeyboardInfo() {
  const { keyboardHeight, isKeyboardVisible, keyboardAnimationDuration } =
    useKeyboardHeight();

  return (
    <View
      style={{
        position: 'absolute',
        top: 100,
        left: 20,
        backgroundColor: 'rgba(0,0,0,0.8)',
        padding: 10,
      }}
    >
      <Text style={{ color: 'white' }}>Height: {keyboardHeight}px</Text>
      <Text style={{ color: 'white' }}>
        Visible: {isKeyboardVisible ? 'Yes' : 'No'}
      </Text>
      <Text style={{ color: 'white' }}>
        Duration: {keyboardAnimationDuration}ms
      </Text>
      <Text style={{ color: 'white' }}>Platform: {Platform.OS}</Text>
    </View>
  );
}
```

## Dependencies

- `react` - Required for hooks functionality
- `react-native` - Required for Keyboard API and platform detection

## Accessibility

The hook supports accessibility by:

- Preserving keyboard navigation patterns
- Maintaining focus management during keyboard transitions
- Supporting screen readers by keeping content visible above keyboard
- Enabling proper scrolling behavior for assistive technologies

## References

Learn more about keyboard handling in React Native:

- [React Native Keyboard API](https://reactnative.dev/docs/keyboard)
- [iOS Keyboard Guidelines](https://developer.apple.com/design/human-interface-guidelines/virtual-keyboards)
- [Android Soft Input Methods](https://developer.android.com/guide/topics/text/creating-input-method)
- [React Navigation Keyboard Handling](https://reactnavigation.org/docs/handling-safe-area/)
