Describe the bug
ToastTransitionProps.position is typed ToastPosition | string, but every other place that describes
the same value uses the pure union ToastPosition, and the runtime only supports the six known positions.
The | string widens the union to plain string (TypeScript subsumes 'top-right' | … | string into
string), so consumers of ToastTransitionProps — i.e. anyone writing a custom transition — lose all
type-safety and autocomplete on position, and the compiler will happily accept an invalid value that
produces no animation at runtime.
Version
react-toastify@11.1.0
Evidence
dist/index.d.ts (built types):
// line 99 — the public option
interface CommonOptions { position?: ToastPosition; ... }
// line 294 — internal toast props
interface ToastProps extends ToastOptions { position: ToastPosition; ... }
// line 278 — the outlier
interface ToastTransitionProps { position: ToastPosition | string; ... } // ⟵ should be ToastPosition
ToastPosition is the closed union:
type ToastPosition = 'top-right' | 'top-center' | 'top-left'
| 'bottom-right' | 'bottom-center' | 'bottom-left';
Why | string is incorrect (not just imprecise):
- The transition builds its animation class by concatenation —
const enterClassName = appendPosition ? \${enter}--${position}` : enter — and the shipped CSS only defines rules for the six known positions (.Toastify__bounce-enter--top-left, --top-right, --top-center, --bottom-left, --bottom-right, --bottom-center). Any other string yields a class like Toastify__bounce-enter--whatever` that matches no rule → no animation (silent breakage).
- The only value ever passed to a transition's
position originates from the container/toast options,
which are typed ToastPosition (CommonOptions.position?: ToastPosition). So the | string arm is
never legitimately reachable through normal use.
Expected behavior
interface ToastTransitionProps {
position: ToastPosition; // closed union, consistent with CommonOptions / ToastProps
}
This restores typo-safety and autocomplete for custom-transition authors and matches what the runtime
actually supports. (If arbitrary positions are intended to be supported in future, the fix would instead
be to add the corresponding CSS — but as shipped, the types are looser than the implementation.)
How I found this
While generating ReScript type bindings from react-toastify's .d.ts, the binding for <Bounce> came out
as position: string (correct per the types) while <ToastContainer> came out as a proper 6-value enum —
the asymmetry traced back to this single | string. Filing since it looks like an unintended widening.
Describe the bug
ToastTransitionProps.positionis typedToastPosition | string, but every other place that describesthe same value uses the pure union
ToastPosition, and the runtime only supports the six known positions.The
| stringwidens the union to plainstring(TypeScript subsumes'top-right' | … | stringintostring), so consumers ofToastTransitionProps— i.e. anyone writing a custom transition — lose alltype-safety and autocomplete on
position, and the compiler will happily accept an invalid value thatproduces no animation at runtime.
Version
react-toastify@11.1.0Evidence
dist/index.d.ts(built types):ToastPositionis the closed union:Why
| stringis incorrect (not just imprecise):const enterClassName = appendPosition ? \${enter}--${position}` : enter— and the shipped CSS only defines rules for the six known positions (.Toastify__bounce-enter--top-left,--top-right,--top-center,--bottom-left,--bottom-right,--bottom-center). Any other string yields a class likeToastify__bounce-enter--whatever` that matches no rule → no animation (silent breakage).positionoriginates from the container/toast options,which are typed
ToastPosition(CommonOptions.position?: ToastPosition). So the| stringarm isnever legitimately reachable through normal use.
Expected behavior
This restores typo-safety and autocomplete for custom-transition authors and matches what the runtime
actually supports. (If arbitrary positions are intended to be supported in future, the fix would instead
be to add the corresponding CSS — but as shipped, the types are looser than the implementation.)
How I found this
While generating ReScript type bindings from react-toastify's
.d.ts, the binding for<Bounce>came outas
position: string(correct per the types) while<ToastContainer>came out as a proper 6-value enum —the asymmetry traced back to this single
| string. Filing since it looks like an unintended widening.