diff --git a/README.md b/README.md
index 12be4371ba..6b65d8a35b 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,7 @@
- [`useWindowSize`](./docs/useWindowSize.md) — tracks `Window` dimensions. [![][img-demo]](https://codesandbox.io/s/m7ln22668)
- [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions using the Resize Observer API.[![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemeasure--demo)
- [`createBreakpoint`](./docs/createBreakpoint.md) — tracks `innerWidth`
+ - [`useScrollbarWidth`](./docs/useScrollbarWidth.md) — detects browser's native scrollbars width. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usescrollbarwidth--demo)
- [**UI**](./docs/UI.md)
diff --git a/docs/useScrollbarWidth.md b/docs/useScrollbarWidth.md
new file mode 100644
index 0000000000..b3246dfa91
--- /dev/null
+++ b/docs/useScrollbarWidth.md
@@ -0,0 +1,25 @@
+# `useScrollbarWidth`
+
+Hook that will return current browser's scrollbar width.
+In case hook been called before DOM ready, it will return `undefined` and will cause re-render on first available RAF.
+> **_NOTE:_** it does not work (return 0) for mobile devices, because their scrollbar width can not be determined.
+
+## Usage
+
+```jsx
+const Demo = () => {
+ const sbw = useScrollbarWidth();
+
+ return (
+
+ {sbw === undefined ? `DOM is not ready yet, SBW detection delayed` : `Browser's scrollbar width is ${sbw}px`}
+
+ );
+};
+```
+
+## Reference
+
+```typescript
+const sbw: number | undefined = useScrollbarWidth();
+```
diff --git a/package.json b/package.json
index 4805ab25df..5d4901d582 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
},
"homepage": "https://github.com/streamich/react-use#readme",
"dependencies": {
+ "@xobotyi/scrollbar-width": "1.5.0",
"copy-to-clipboard": "^3.2.0",
"nano-css": "^5.2.1",
"react-fast-compare": "^2.0.4",
diff --git a/src/__stories__/useScrollbarWidth.story.tsx b/src/__stories__/useScrollbarWidth.story.tsx
new file mode 100644
index 0000000000..2225f0f4fb
--- /dev/null
+++ b/src/__stories__/useScrollbarWidth.story.tsx
@@ -0,0 +1,18 @@
+import { storiesOf } from '@storybook/react';
+import * as React from 'react';
+import { useScrollbarWidth } from '..';
+import ShowDocs from './util/ShowDocs';
+
+const Demo = () => {
+ const sbw = useScrollbarWidth();
+
+ return (
+
+ {sbw === undefined ? `DOM is not ready yet, SBW detection delayed` : `Browser's scrollbar width is ${sbw}px`}
+
+ );
+};
+
+storiesOf('Sensors/useScrollbarWidth', module)
+ .add('Docs', () => )
+ .add('Demo', () => );
diff --git a/src/index.ts b/src/index.ts
index 61ef03e3a3..9d78994a27 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -92,6 +92,7 @@ export { default as useUpsert } from './useUpsert';
export { default as useVibrate } from './useVibrate';
export { default as useVideo } from './useVideo';
export { default as useStateValidator } from './useStateValidator';
+export { useScrollbarWidth } from './useScrollbarWidth';
export { useMultiStateValidator } from './useMultiStateValidator';
export { default as useWindowScroll } from './useWindowScroll';
export { default as useWindowSize } from './useWindowSize';
diff --git a/src/useScrollbarWidth.ts b/src/useScrollbarWidth.ts
new file mode 100644
index 0000000000..ff637ce4f9
--- /dev/null
+++ b/src/useScrollbarWidth.ts
@@ -0,0 +1,21 @@
+import { scrollbarWidth } from '@xobotyi/scrollbar-width';
+import { useEffect, useState } from 'react';
+
+export function useScrollbarWidth(): number | undefined {
+ const [sbw, setSbw] = useState(scrollbarWidth());
+
+ // this needed to ensure the scrollbar width in case hook called before the DOM is ready
+ useEffect(() => {
+ if (typeof sbw !== 'undefined') {
+ return;
+ }
+
+ const raf = requestAnimationFrame(() => {
+ setSbw(scrollbarWidth());
+ });
+
+ return () => cancelAnimationFrame(raf);
+ }, []);
+
+ return sbw;
+}
diff --git a/tests/useScrollbarWidth.test.ts b/tests/useScrollbarWidth.test.ts
new file mode 100644
index 0000000000..26a60aa423
--- /dev/null
+++ b/tests/useScrollbarWidth.test.ts
@@ -0,0 +1,45 @@
+import { act, renderHook } from '@testing-library/react-hooks';
+import { scrollbarWidth } from '@xobotyi/scrollbar-width';
+import { useScrollbarWidth } from '../src';
+import { replaceRaf } from 'raf-stub';
+
+declare var requestAnimationFrame: {
+ add: (cb: Function) => number;
+ remove: (id: number) => void;
+ flush: (duration?: number) => void;
+ reset: () => void;
+ step: (steps?: number, duration?: number) => void;
+};
+
+describe('useScrollbarWidth', () => {
+ beforeAll(() => {
+ replaceRaf();
+ });
+
+ afterEach(() => {
+ requestAnimationFrame.reset();
+ });
+
+ it('should be defined', () => {
+ expect(useScrollbarWidth).toBeDefined();
+ });
+
+ it('should return value of scrollbarWidth result', () => {
+ scrollbarWidth.__cache = 21;
+ const { result } = renderHook(() => useScrollbarWidth());
+
+ expect(result.current).toBe(21);
+ });
+
+ it('should re-call scrollbar width in RAF in case `scrollbarWidth()` returned undefined', () => {
+ scrollbarWidth.__cache = undefined;
+ const { result } = renderHook(() => useScrollbarWidth());
+ expect(result.current).toBe(undefined);
+ scrollbarWidth.__cache = 34;
+ act(() => {
+ requestAnimationFrame.step();
+ });
+
+ expect(result.current).toBe(34);
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index 31ebec02d3..770649f50a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3094,6 +3094,11 @@
"@webassemblyjs/wast-parser" "1.8.5"
"@xtuc/long" "4.2.2"
+"@xobotyi/scrollbar-width@1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.5.0.tgz#488210bff634548040dc22a72f62722a85b134e1"
+ integrity sha512-BK+HR1D00F2xh7n4+5en8/dMkG13uvIXLmEbsjtc1702b7+VwXkvlBDKoRPJMbkRN5hD7VqWa3nS9fNT8JG3CA==
+
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"